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

import {IRouterClient} from "../ccip/IRouterClient.sol";
import {ReserveCommitmentStore} from "./ReserveCommitmentStore.sol";

/**
 * @title OMNLMirrorReceiver
 * @notice CCIP receiver on the mirror chain (e.g. 651940) applying mirrored reserve commits from primary (e.g. 138).
 * @dev Router must be the canonical CCIP router; allowlist source selectors to prevent spoofed lanes.
 */
contract OMNLMirrorReceiver {
    IRouterClient public immutable router;
    ReserveCommitmentStore public immutable reserveStore;
    address public admin;

    mapping(bytes32 => bool) public processedMessages;
    mapping(uint64 => bool) public allowedSourceSelectors;

    event MirrorCommitReceived(
        bytes32 indexed messageId,
        uint64 indexed sourceChainSelector,
        bytes32 indexed lineId,
        uint256 version,
        uint256 R,
        bytes32 merkleRoot
    );
    event SourceSelectorSet(uint64 indexed selector, bool allowed);
    event AdminTransferred(address indexed previous, address indexed next);

    error OnlyRouter();
    error OnlyAdmin();
    error AlreadyProcessed();
    error SourceNotAllowed();

    modifier onlyRouter() {
        if (msg.sender != address(router)) revert OnlyRouter();
        _;
    }

    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), "OMNLMirrorReceiver: zero");
        router = IRouterClient(router_);
        reserveStore = ReserveCommitmentStore(reserveStore_);
        admin = admin_;
    }

    function setSourceAllowed(uint64 sourceChainSelector, bool allowed) external onlyAdmin {
        allowedSourceSelectors[sourceChainSelector] = allowed;
        emit SourceSelectorSet(sourceChainSelector, allowed);
    }

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

    /**
     * @notice CCIP entrypoint — payload abi.encode(version, lineId, R, validUntil, evidenceHash, merkleRoot)
     */
    function ccipReceive(IRouterClient.Any2EVMMessage calldata message) external onlyRouter {
        if (processedMessages[message.messageId]) revert AlreadyProcessed();
        if (!allowedSourceSelectors[message.sourceChainSelector]) revert SourceNotAllowed();

        processedMessages[message.messageId] = true;

        (uint256 version, bytes32 lineId, uint256 R, uint256 validUntil, bytes32 evidenceHash, bytes32 merkleRoot) =
            abi.decode(message.data, (uint256, bytes32, uint256, uint256, bytes32, bytes32));

        reserveStore.applyMirrorCommit(lineId, R, validUntil, evidenceHash, merkleRoot, version);

        emit MirrorCommitReceived(message.messageId, message.sourceChainSelector, lineId, version, R, merkleRoot);
    }
}
