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

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IRouterClient} from "../ccip/IRouterClient.sol";
import {ReserveCommitmentStore} from "./ReserveCommitmentStore.sol";

/**
 * @title OMNLMirrorCoordinator
 * @notice Primary-chain helper: commits to ReserveCommitmentStore then sends data-only CCIP to mirror receiver.
 * @dev Grant RESERVE_COMMITTER_ROLE to this contract on ReserveCommitmentStore.
 *      Native fee: send `msg.value >= fee`. ERC-20 fee (e.g. LINK): approve this contract for `fee` before calling.
 */
contract OMNLMirrorCoordinator {
    using SafeERC20 for IERC20;

    IRouterClient public immutable ccipRouter;
    ReserveCommitmentStore public immutable reserveStore;
    address public admin;

    uint64 public mirrorChainSelector;
    address public mirrorReceiver;
    address public feeToken;

    event MirrorChainConfigured(uint64 indexed selector, address indexed receiver, address feeToken);
    event MirrorMessageSent(bytes32 indexed messageId, uint64 indexed destSelector, bytes32 indexed lineId, uint256 version);

    error OnlyAdmin();

    modifier onlyAdmin() {
        if (msg.sender != admin) revert OnlyAdmin();
        _;
    }

    constructor(address router_, address reserveStore_, address admin_) {
        require(router_ != address(0) && reserveStore_ != address(0) && admin_ != address(0), "OMNLMirrorCoordinator: zero");
        ccipRouter = IRouterClient(router_);
        reserveStore = ReserveCommitmentStore(reserveStore_);
        admin = admin_;
    }

    function setMirrorDestination(uint64 selector, address receiver, address feeToken_) external onlyAdmin {
        mirrorChainSelector = selector;
        mirrorReceiver = receiver;
        feeToken = feeToken_;
        emit MirrorChainConfigured(selector, receiver, feeToken_);
    }

    function transferAdmin(address next) external onlyAdmin {
        require(next != address(0), "OMNLMirrorCoordinator: zero");
        admin = next;
    }

    /// @notice Commit on primary then mirror state via CCIP (data-only message).
    function commitReserveAndMirror(
        bytes32 lineId,
        uint256 R,
        uint256 validUntil,
        bytes32 evidenceHash,
        bytes32 merkleRoot
    ) external payable returns (bytes32 messageId) {
        require(mirrorReceiver != address(0) && mirrorChainSelector != 0, "OMNLMirrorCoordinator: mirror unset");
        reserveStore.commitReserve(lineId, R, validUntil, evidenceHash, merkleRoot);
        ReserveCommitmentStore.Commitment memory c = reserveStore.getCommitment(lineId);
        bytes memory data = abi.encode(c.version, lineId, R, validUntil, evidenceHash, merkleRoot);

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

        uint256 fee = ccipRouter.getFee(mirrorChainSelector, message);
        if (feeToken == address(0)) {
            require(msg.value >= fee, "OMNLMirrorCoordinator: fee");
            (messageId,) = ccipRouter.ccipSend{value: fee}(mirrorChainSelector, message);
        } else {
            IERC20 ft = IERC20(feeToken);
            ft.safeTransferFrom(msg.sender, address(this), fee);
            SafeERC20.safeIncreaseAllowance(ft, address(ccipRouter), fee);
            (messageId,) = ccipRouter.ccipSend(mirrorChainSelector, message);
        }

        emit MirrorMessageSent(messageId, mirrorChainSelector, lineId, c.version);
    }

    function quoteMirrorFee(
        bytes32 lineId,
        uint256 R,
        uint256 validUntil,
        bytes32 evidenceHash,
        bytes32 merkleRoot
    ) external view returns (uint256 fee) {
        ReserveCommitmentStore.Commitment memory c = reserveStore.getCommitment(lineId);
        uint256 nextVersion = c.version + 1;
        bytes memory data = abi.encode(nextVersion, lineId, R, validUntil, evidenceHash, merkleRoot);
        IRouterClient.EVM2AnyMessage memory message = IRouterClient.EVM2AnyMessage({
            receiver: abi.encode(mirrorReceiver),
            data: data,
            tokenAmounts: new IRouterClient.TokenAmount[](0),
            feeToken: feeToken,
            extraArgs: ""
        });
        return ccipRouter.getFee(mirrorChainSelector, message);
    }
}
