// File: contracts/adapters/Sygma/interfaces/IBridge.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;

/**
    @title Interface for Bridge contract.
    @author ChainSafe Systems.
 */
interface IBridge {
    /**
        @notice Initiates a transfer using a specified handler contract.
        @notice Only callable when Bridge is not paused.
        @param destinationDomainID ID of chain deposit will be bridged to.
        @param resourceID ResourceID used to find address of handler to be used for deposit.
        @param depositData Additional data to be passed to specified handler.
        @param feeData Additional data to be passed to the fee handler.
        @notice Emits {Deposit} event with all necessary parameters.
     */
    function deposit(
        uint8 destinationDomainID,
        bytes32 resourceID,
        bytes calldata depositData,
        bytes calldata feeData
    ) external payable returns (uint64 depositNonce, bytes memory handlerResponse);
}

// File: contracts/adapters/Sygma/interfaces/ISygmaAdapter.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;

interface ISygmaAdapter {
    function storeHashes(address reporter, uint256[] memory ids, bytes32[] memory _hashes) external;
}

// File: contracts/adapters/Sygma/SygmaHeaderReporter.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { SygmaReporter } from "./SygmaReporter.sol";
import { HeaderStorage } from "../../utils/HeaderStorage.sol";

contract SygmaHeaderReporter is SygmaReporter {
    HeaderStorage public immutable _headerStorage;

    event HeaderReported(address indexed emitter, uint256 indexed blockNumber, bytes32 indexed blockHeader);

    constructor(
        address bridge,
        HeaderStorage headerStorage,
        bytes32 resourceID,
        uint8 defaultDestinationDomainID,
        address defaultSygmaAdapter
    ) SygmaReporter(bridge, resourceID, defaultDestinationDomainID, defaultSygmaAdapter) {
        _headerStorage = headerStorage;
    }

    /**
        @dev Reports the given block headers to the oracleAdapter via the Sygma bridge to default domain.
        @param blockNumbers Uint256 array of block numbers to pass over the Sygma bridge.
        @param feeData Additional data to be passed to the fee handler.
    */
    function reportHeaders(uint256[] memory blockNumbers, bytes calldata feeData) public payable {
        _reportHeaders(blockNumbers, _defaultSygmaAdapter, _defaultDestinationDomainID, feeData);
    }

    /**
        @dev Reports the given block headers to the oracleAdapter via the Sygma bridge to specified domain.
        @param blockNumbers Uint256 array of block numbers to pass over the Sygma bridge.
        @param sygmaAdapter Address of the Sygma adapter on the target chain.
        @param destinationDomainID Destination domain ID.
        @param feeData Additional data to be passed to the fee handler.
    */
    function reportHeadersToDomain(
        uint256[] memory blockNumbers,
        address sygmaAdapter,
        uint8 destinationDomainID,
        bytes memory feeData
    ) public payable {
        _reportHeaders(blockNumbers, sygmaAdapter, destinationDomainID, feeData);
    }

    function _reportHeaders(
        uint256[] memory blockNumbers,
        address sygmaAdapter,
        uint8 destinationDomainID,
        bytes memory feeData
    ) internal {
        bytes32[] memory blockHeaders = _headerStorage.storeBlockHeaders(blockNumbers);
        _reportData(blockNumbers, blockHeaders, sygmaAdapter, destinationDomainID, feeData);
        for (uint i = 0; i < blockNumbers.length; i++) {
            emit HeaderReported(address(this), blockNumbers[i], blockHeaders[i]);
        }
    }
}

// File: contracts/adapters/Sygma/SygmaReporter.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import "./interfaces/ISygmaAdapter.sol";
import "./interfaces/IBridge.sol";

contract SygmaReporter {
    address public immutable _bridge;
    bytes32 public immutable _resourceID;
    uint8 public immutable _defaultDestinationDomainID;
    address public immutable _defaultSygmaAdapter;

    constructor(address bridge, bytes32 resourceID, uint8 defaultDestinationDomainID, address defaultSygmaAdapter) {
        _bridge = bridge;
        _resourceID = resourceID;
        _defaultDestinationDomainID = defaultDestinationDomainID;
        _defaultSygmaAdapter = defaultSygmaAdapter;
    }

    function _reportData(
        uint256[] memory messageIds,
        bytes32[] memory hashes,
        address sygmaAdapter,
        uint8 destinationDomainID,
        bytes memory feeData
    ) internal returns (uint64 depositNonce, bytes memory handlerResponse) {
        bytes memory depositData = abi.encodePacked(
            // uint256 maxFee
            uint256(0),
            // uint16 len(executeFuncSignature)
            uint16(4),
            // bytes executeFuncSignature
            ISygmaAdapter(address(0)).storeHashes.selector,
            // uint8 len(executeContractAddress)
            uint8(20),
            // bytes executeContractAddress
            sygmaAdapter,
            // uint8 len(executionDataDepositor)
            uint8(20),
            // bytes executionDataDepositor
            address(this),
            // bytes executionDataDepositor + executionData
            prepareDepositData(messageIds, hashes)
        );
        return IBridge(_bridge).deposit{ value: msg.value }(destinationDomainID, _resourceID, depositData, feeData);
    }

    function slice(bytes calldata input, uint256 position) public pure returns (bytes memory) {
        return input[position:];
    }

    function prepareDepositData(
        uint256[] memory messageIds,
        bytes32[] memory hashes
    ) public view returns (bytes memory) {
        bytes memory encoded = abi.encode(address(0), messageIds, hashes);
        return this.slice(encoded, 32);
    }
}

// File: contracts/interfaces/IHeaderStorage.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

interface IHeaderStorage {
    event HeaderStored(uint256 indexed blockNumber, bytes32 indexed blockHeader);

    error HeaderOutOfRange(address emitter, uint256 blockNumber);

    function storeBlockHeader(uint256 blockNumber) external returns (bytes32 blockHeader);

    function storeBlockHeaders(uint256[] memory blockNumbers) external returns (bytes32[] memory);
}

// File: contracts/utils/HeaderStorage.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { IHeaderStorage } from "../interfaces/IHeaderStorage.sol";

contract HeaderStorage is IHeaderStorage {
    mapping(uint256 => bytes32) public headers;

    /// @dev Stores and returns the header for the given block.
    /// @param blockNumber Block number.
    /// @return blockHeader Block header stored.
    /// @notice Reverts if the given block header was not previously stored and is now out of range.
    function storeBlockHeader(uint256 blockNumber) public returns (bytes32 blockHeader) {
        blockHeader = headers[blockNumber];
        if (blockHeader == 0) {
            blockHeader = blockhash(blockNumber);
            if (blockHeader == 0) revert HeaderOutOfRange(address(this), blockNumber);
            headers[blockNumber] = blockHeader;
            emit HeaderStored(blockNumber, blockHeader);
        }
    }

    /// @dev Stores and returns the header for an array of given blocks.
    /// @param blockNumbers Array of block numbers.
    /// @return Array of block headers.
    /// @notice Reverts if the given block header was not previously stored and is now out of range.
    function storeBlockHeaders(uint256[] memory blockNumbers) public returns (bytes32[] memory) {
        bytes32[] memory blockHeaders = new bytes32[](blockNumbers.length);
        for (uint256 i = 0; i < blockNumbers.length; i++) {
            blockHeaders[i] = storeBlockHeader(blockNumbers[i]);
        }
        return blockHeaders;
    }
}