// SPDX-License-Identifier: MIT

pragma solidity >=0.8.9;

import "./SwapBase.sol";
import "../../interfaces/IUniswapRouterV3.sol";

contract TransferSwapV3 is SwapBase {
    using SafeERC20Upgradeable for IERC20Upgradeable;
    using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;

    function transferWithSwapV3Native(
        address _receiver,
        uint256 _amountIn,
        uint64 _dstChainId,
        SwapInfoV3 calldata _srcSwap,
        SwapInfoDest calldata _dstSwap,
        uint32 _maxBridgeSlippage
    ) external payable {
        uint256 _fee = _deriveFeeAndPerformChecksNative(
            _amountIn,
            _dstChainId,
            _dstSwap.integrator,
            address(_getFirstBytes20(_srcSwap.path))
        );

        _swapAndSendMessageV3(
            _receiver,
            _amountIn,
            _dstChainId,
            _srcSwap,
            _dstSwap,
            _maxBridgeSlippage,
            _fee,
            address(_getLastBytes20(_srcSwap.path))
        );
    }

    function transferWithSwapV3(
        address _receiver,
        uint256 _amountIn,
        uint64 _dstChainId,
        SwapInfoV3 calldata _srcSwap,
        SwapInfoDest calldata _dstSwap,
        uint32 _maxBridgeSlippage
    ) external payable {
        uint256 _fee = _deriveFeeAndPerformChecks(
            _amountIn,
            _dstChainId,
            _dstSwap.integrator,
            address(_getFirstBytes20(_srcSwap.path))
        );

        _swapAndSendMessageV3(
            _receiver,
            _amountIn,
            _dstChainId,
            _srcSwap,
            _dstSwap,
            _maxBridgeSlippage,
            _fee,
            address(_getLastBytes20(_srcSwap.path))
        );
    }

    /**
     * @notice Sends a cross-chain transfer via the liquidity pool-based bridge and sends a message specifying a wanted swap action on the
               destination chain via the message bus
     * @param _receiver the app contract that implements the MessageReceiver abstract contract
     *        NOTE not to be confused with the receiver field in SwapInfoV3 which is an EOA address of a user
     * @param _amountIn the input amount that the user wants to swap and/or bridge
     * @param _dstChainId destination chain ID
     * @param _srcSwap a struct containing swap related requirements
     * @param _dstSwap a struct containing swap related requirements
     * @param _maxBridgeSlippage the max acceptable slippage at bridge, given as percentage in point (pip). Eg. 5000 means 0.5%.
     *        Must be greater than minimalMaxSlippage. Receiver is guaranteed to receive at least (100% - max slippage percentage) * amount or the
     *        transfer can be refunded.
     * @param _fee the fee to pay to MessageBus.
     */
    function _swapAndSendMessageV3(
        address _receiver,
        uint256 _amountIn,
        uint64 _dstChainId,
        SwapInfoV3 calldata _srcSwap,
        SwapInfoDest calldata _dstSwap,
        uint32 _maxBridgeSlippage,
        uint256 _fee,
        address srcOutputToken
    ) private {
        BaseCrossChainParams memory _baseParams = BaseCrossChainParams(
            address(_getFirstBytes20(_srcSwap.path)),
            _amountIn,
            _dstChainId,
            _retrieveDstTokenAddress(_dstSwap),
            _dstSwap.amountOutMinimum,
            msg.sender,
            _dstSwap.integrator,
            _srcSwap.dex
        );

        require(_srcSwap.path.length > 20, "empty swap path");

        (bool success, uint256 srcAmtOut) = _trySwapV3(_srcSwap, _amountIn);

        bytes32 id = _sendMessage(
            _receiver,
            uint64(_baseParams.dstChainID),
            _dstSwap,
            _maxBridgeSlippage,
            _beforeSwapAndSendMessage(), // TODO rename
            _fee,
            srcOutputToken,
            srcAmtOut,
            success
        );

        emit CrossChainRequestSent(id, _baseParams);
    }

    function _trySwapV3(SwapInfoV3 memory _swap, uint256 _amount)
        internal
        returns (bool ok, uint256 amountOut)
    {
        if (!availableRouters.contains(_swap.dex)) {
            return (false, 0);
        }

        SmartApprove.smartApprove(
            address(_getFirstBytes20(_swap.path)),
            _amount,
            _swap.dex
        );

        IUniswapRouterV3.ExactInputParams memory paramsV3 = IUniswapRouterV3
            .ExactInputParams(
                _swap.path,
                address(this),
                _swap.deadline,
                _amount,
                _swap.amountOutMinimum
            );

        try IUniswapRouterV3(_swap.dex).exactInput(paramsV3) returns (
            uint256 _amountOut
        ) {
            return (true, _amountOut);
        } catch {
            return (false, 0);
        }
    }
}
