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

import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "../../vendor/openzeppelin/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "../../ccip/IRouterClient.sol";
import {IChain138BatchEmitter} from "../interfaces/IChain138BatchEmitter.sol";
import {BatchEmitterConfig} from "../libraries/BatchEmitterConfig.sol";

/**
 * @title Chain138BatchEmitter
 * @notice Chain 138: commit batch + CCIP to mainnet Chain138MainnetCheckpoint.
 */
contract Chain138BatchEmitter is Initializable, AccessControlUpgradeable, UUPSUpgradeable, IChain138BatchEmitter {
    bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
    bytes32 public constant COMMITTER_ROLE = keccak256("COMMITTER_ROLE");

    IRouterClient public ccipRouter;
    address public mainnetCheckpoint;
    address public linkToken;
    uint64 public mainnetChainSelector;
    mapping(uint64 => bool) public committed;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize(
        address admin,
        address _ccipRouter,
        address _linkToken,
        uint64 _mainnetChainSelector,
        address _mainnetCheckpoint
    ) external initializer {
        __AccessControl_init();
        __UUPSUpgradeable_init();
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(UPGRADER_ROLE, admin);
        _grantRole(COMMITTER_ROLE, admin);
        ccipRouter = IRouterClient(_ccipRouter);
        linkToken = _linkToken;
        mainnetChainSelector = _mainnetChainSelector;
        mainnetCheckpoint = _mainnetCheckpoint;
    }

    function commitBatch(
        uint64 batchId,
        bytes32 paymentsRoot,
        uint256 checkpointBlock,
        uint256 startBlock,
        uint16 txCount,
        bytes32[] calldata txHashes
    ) external onlyRole(COMMITTER_ROLE) {
        require(!committed[batchId], "committed");
        require(paymentsRoot != bytes32(0), "root");
        committed[batchId] = true;
        emit BatchCommittedOn138(batchId, paymentsRoot, checkpointBlock, txCount);
        // txHashes emitted off-chain or via separate event in production
        startBlock;
        txHashes;
    }

    function sendBatchToMainnet(
        uint64 batchId,
        bytes calldata encodedCheckpointPayload,
        uint256 linkFee
    ) external onlyRole(COMMITTER_ROLE) returns (bytes32 ccipMessageId) {
        require(committed[batchId], "not committed");
        require(mainnetCheckpoint != address(0), "no dest");

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

        (ccipMessageId,) = ccipRouter.ccipSend(mainnetChainSelector, message);
        linkFee;
        emit BatchSentToMainnet(batchId, ccipMessageId);
    }

    function setMainnetCheckpoint(address _mainnetCheckpoint) external onlyRole(DEFAULT_ADMIN_ROLE) {
        mainnetCheckpoint = _mainnetCheckpoint;
    }

    function setCcipRouter(address _ccipRouter) external onlyRole(DEFAULT_ADMIN_ROLE) {
        ccipRouter = IRouterClient(_ccipRouter);
    }

    function setLinkToken(address _linkToken) external onlyRole(DEFAULT_ADMIN_ROLE) {
        linkToken = _linkToken;
    }

    function setMainnetChainSelector(uint64 selector) external onlyRole(DEFAULT_ADMIN_ROLE) {
        mainnetChainSelector = selector;
    }

    function getEmitterConfig() external view returns (BatchEmitterConfig.EmitterConfig memory cfg) {
        cfg.ccipRouter = address(ccipRouter);
        cfg.linkToken = linkToken;
        cfg.mainnetChainSelector = mainnetChainSelector;
        cfg.mainnetCheckpoint = mainnetCheckpoint;
    }

    function applyEmitterConfig(BatchEmitterConfig.EmitterConfig calldata cfg)
        external
        onlyRole(DEFAULT_ADMIN_ROLE)
    {
        BatchEmitterConfig.validate(cfg);
        ccipRouter = IRouterClient(cfg.ccipRouter);
        linkToken = cfg.linkToken;
        mainnetChainSelector = cfg.mainnetChainSelector;
        mainnetCheckpoint = cfg.mainnetCheckpoint;
    }

    function _authorizeUpgrade(address) internal override onlyRole(UPGRADER_ROLE) {}
}
