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

import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ICrossChainFlashBridge} from "./interfaces/ICrossChainFlashBridge.sol";

/**
 * @title CrossChainFlashBorrower
 * @notice ERC-3156 borrower: flash `token` on **this** chain → bridge `bridgeAmount` out → repay `amount + fee` to the flash vault from **on-hand** balance.
 * @dev **Atomicity is single-chain only.** CCIP / bridge finality is asynchronous; destination delivery cannot complete inside this callback.
 *      **Prefunding:** before `flashLoan`, this contract should hold at least `bridgeAmount + fee` of `token`
 *      (or `amount + fee` if `bridgeAmount == amount`) so repayment succeeds after the bridge pulls `bridgeAmount`.
 *      Encode `CrossChainFlashParams` in `data`.
 */
contract CrossChainFlashBorrower is IERC3156FlashBorrower {
    using SafeERC20 for IERC20;

    bytes32 private constant _RETURN_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan");

    address public immutable trustedLender;

    struct CrossChainFlashParams {
        address bridge;
        uint256 bridgeAmount;
        uint64 destinationChainSelector;
        address recipientOnDestination;
        bytes bridgeExtraData;
        uint256 nativeBridgeFee;
    }

    error UntrustedLender();
    error BadParams();
    error InsufficientToRepay();

    constructor(address trustedLender_) {
        trustedLender = trustedLender_;
    }

    function onFlashLoan(
        address,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external override returns (bytes32) {
        if (msg.sender != trustedLender) revert UntrustedLender();
        _run(abi.decode(data, (CrossChainFlashParams)), token, amount, fee);
        return _RETURN_VALUE;
    }

    function _run(CrossChainFlashParams memory p, address token, uint256 amount, uint256 fee) internal {
        if (p.bridge == address(0) || p.recipientOnDestination == address(0)) revert BadParams();
        if (p.bridgeAmount == 0 || p.bridgeAmount > amount) revert BadParams();

        IERC20 t = IERC20(token);
        t.forceApprove(p.bridge, p.bridgeAmount);
        ICrossChainFlashBridge(p.bridge).bridgeTokensFrom{value: p.nativeBridgeFee}(
            token,
            p.bridgeAmount,
            p.destinationChainSelector,
            p.recipientOnDestination,
            p.bridgeExtraData
        );

        uint256 need = amount + fee;
        if (t.balanceOf(address(this)) < need) revert InsufficientToRepay();
        t.safeTransfer(msg.sender, need);
    }

    receive() external payable {}
}
