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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../UniversalCCIPBridge.sol";
import "./EnhancedSwapRouter.sol";

/**
 * @title SwapBridgeSwapCoordinator
 * @notice Coordinates source-chain swap (token A -> bridgeable token) then CCIP bridge in one flow
 * @dev User approves coordinator for sourceToken; coordinator swaps via EnhancedSwapRouter (Dodoex) then calls UniversalCCIPBridge
 */
contract SwapBridgeSwapCoordinator is ReentrancyGuard {
    using SafeERC20 for IERC20;

    EnhancedSwapRouter public immutable swapRouter;
    UniversalCCIPBridge public immutable bridge;

    event SwapAndBridgeExecuted(
        address indexed sourceToken,
        address indexed bridgeableToken,
        uint256 amountIn,
        uint256 amountBridged,
        uint64 destinationChain,
        address indexed recipient,
        bytes32 messageId
    );

    error ZeroAddress();
    error ZeroAmount();
    error InsufficientOutput();
    error SameToken();

    constructor(address _swapRouter, address _bridge) {
        if (_swapRouter == address(0) || _bridge == address(0)) revert ZeroAddress();
        swapRouter = EnhancedSwapRouter(payable(_swapRouter));
        bridge = UniversalCCIPBridge(payable(_bridge));
    }

    /**
     * @notice Swap source token to bridgeable token then bridge to destination chain
     * @param sourceToken Token user is sending (will be swapped if different from bridgeableToken)
     * @param amountIn Amount of source token
     * @param amountOutMin Minimum bridgeable token from swap (slippage protection; ignored if sourceToken == bridgeableToken)
     * @param bridgeableToken Token to bridge (WETH or stablecoin); must be registered on bridge
     * @param destinationChainSelector CCIP destination chain selector
     * @param recipient Recipient on destination chain
     * @param assetType Asset type hash for bridge (from UniversalAssetRegistry)
     * @param usePMM Whether bridge should use PMM liquidity
     * @param useVault Whether bridge should use vault
     */
    function swapAndBridge(
        address sourceToken,
        uint256 amountIn,
        uint256 amountOutMin,
        address bridgeableToken,
        uint64 destinationChainSelector,
        address recipient,
        bytes32 assetType,
        bool usePMM,
        bool useVault
    ) external payable nonReentrant returns (bytes32 messageId) {
        if (amountIn == 0) revert ZeroAmount();
        if (sourceToken == address(0) || bridgeableToken == address(0) || recipient == address(0)) revert ZeroAddress();

        uint256 amountToBridge;

        if (sourceToken == bridgeableToken) {
            IERC20(sourceToken).safeTransferFrom(msg.sender, address(this), amountIn);
            amountToBridge = amountIn;
        } else {
            IERC20(sourceToken).safeTransferFrom(msg.sender, address(this), amountIn);
            IERC20(sourceToken).approve(address(swapRouter), amountIn);
            amountToBridge = swapRouter.swapTokenToToken(sourceToken, bridgeableToken, amountIn, amountOutMin);
            if (amountToBridge < amountOutMin) revert InsufficientOutput();
        }

        UniversalCCIPBridge.BridgeOperation memory op = UniversalCCIPBridge.BridgeOperation({
            token: bridgeableToken,
            amount: amountToBridge,
            destinationChain: destinationChainSelector,
            recipient: recipient,
            assetType: assetType,
            usePMM: usePMM,
            useVault: useVault,
            complianceProof: "",
            vaultInstructions: ""
        });

        IERC20(bridgeableToken).approve(address(bridge), amountToBridge);
        (bool ok, bytes memory result) = address(bridge).call{value: msg.value}(
            abi.encodeWithSelector(bridge.bridge.selector, op)
        );
        require(ok, "SwapBridgeSwapCoordinator: bridge failed");
        messageId = abi.decode(result, (bytes32));

        emit SwapAndBridgeExecuted(
            sourceToken,
            bridgeableToken,
            amountIn,
            amountToBridge,
            destinationChainSelector,
            recipient,
            messageId
        );
        return messageId;
    }

    receive() external payable {}
}
