contract BBZOrigin is Ownable, ERC721A, ReentrancyGuard, IOwnershipData {
    struct MintConfig {
        uint256 dnmAmount;
        bool groupAActive;
        uint256 groupAPrice;
        bool groupBActive;
        uint256 groupBPrice;
        bool groupCActive;
        uint256 groupCPrice;
    }

    MintConfig public config;

    mapping(address => uint256) public groupA;
    mapping(address => uint256) public groupB;

    string private baseTokenURI;

    constructor(
        uint256 collectionSize,
        uint256 dnmAmount
    ) ERC721A("BBZ Origin", "BBZORIGIN", 5, collectionSize) {
      config.dnmAmount = dnmAmount;
      config.groupAPrice = 0.1 ether;
      config.groupBPrice = 0.12 ether;
      config.groupCPrice = 0.14 ether;
      baseTokenURI = "https://d359zl9sgwt1qi.cloudfront.net/";
    }

    modifier callerIsNotContract() {
        require(tx.origin == msg.sender, "The caller is another contract");
        _;
    }

    /* DnM Related */
    function mintDnM() external onlyOwner {
        uint256 numChunks = config.dnmAmount / maxBatchSize;
        for (uint256 i = 0; i < numChunks; i++) {
            _safeMint(msg.sender, maxBatchSize);
        }
    }

    function mintUnclaimed() external onlyOwner {
        for (uint256 i = currentIndex; i < collectionSize; i++) {
            _safeMint(msg.sender, 1);
        }
    }

    /* Manage */
    function setGroupState(bool g1, bool g2, bool g3) external onlyOwner {
        config.groupAActive = g1;
        config.groupBActive = g2;
        config.groupCActive = g3;
    }

    function setGroupPrice(uint256 p1, uint256 p2, uint256 p3) external onlyOwner {
        config.groupAPrice = p1;
        config.groupBPrice = p2;
        config.groupCPrice = p3;
    }

    function withdrawETH() external onlyOwner nonReentrant {
        (bool success, ) = msg.sender.call{value: address(this).balance}("");
        require(success, "Transfer failed.");
    }

    /* OG Mint */
    function isOGMintActive() external view returns (bool) {
        return config.groupAActive;
    }

    function isInOgList() external view returns (bool) {
        return (groupA[msg.sender] > 0);
    }

    function mintOG(uint256 quantity)
        external
        payable
        callerIsNotContract
    {
        uint256 totalCost = uint256(config.groupAPrice) * quantity;

        require(config.groupAActive, "OG Sale is not active");
        require(msg.value >= totalCost, "Not enough ETH");
        require(groupA[msg.sender] >= quantity, "Not in OG list or already minted max amount");
        require(currentIndex + quantity <= collectionSize, "Total tokens amount reached");

        groupA[msg.sender] = groupA[msg.sender] - quantity;
        _safeMint(msg.sender, quantity);
    }

    function setOGList(
        address[] memory addresses,
        uint256[] memory numSlots
    ) external onlyOwner {
        require(
            addresses.length == numSlots.length,
            "Addresses does not match numSlots length"
        );
        for (uint256 i = 0; i < addresses.length; i++) {
            groupA[addresses[i]] += numSlots[i];
        }
    }

    /* WL Mint */
    function isWLMintActive() external view returns (bool) {
        return config.groupBActive;
    }

    function isInWLList() external view returns (bool) {
        return (groupB[msg.sender] > 0);
    }

    function mintWL(uint256 quantity)
        external
        payable
        callerIsNotContract
    {
        uint256 totalCost = uint256(config.groupBPrice) * quantity;

        require(config.groupBActive, "WL Sale is not active");
        require(msg.value >= totalCost, "Not enough ETH");
        require(groupB[msg.sender] >= quantity, "Not in WL list or already minted max amount");
        require(currentIndex + quantity <= collectionSize, "Total tokens amount reached");

        groupB[msg.sender] = groupB[msg.sender] - quantity;
        _safeMint(msg.sender, quantity);
    }

    function setWLList(
        address[] memory addresses,
        uint256[] memory numSlots
    ) external onlyOwner {
        require(
            addresses.length == numSlots.length,
            "Addresses does not match numSlots length"
        );
        for (uint256 i = 0; i < addresses.length; i++) {
            groupB[addresses[i]] += numSlots[i];
        }
    }

    /* Public Mint */
    function isPublicMintActive() external view returns (bool) {
        return config.groupCActive;
    }

    function mint(uint256 quantity)
        external
        payable
        callerIsNotContract
    {
        uint256 totalCost = uint256(config.groupCPrice) * quantity;

        require(config.groupCActive, "Public Sale is not active");
        require(msg.value >= totalCost, "Not enough ETH");
        require(currentIndex + quantity <= collectionSize, "Total tokens amount reached");
        require(quantity <= 3, "Max mint amount is 3");

        _safeMint(msg.sender, quantity);
    }

    /* Metadata */
    function _baseURI() internal view virtual override returns (string memory) {
        return baseTokenURI;
    }

    function setBaseURI(string calldata baseURI) external onlyOwner {
        baseTokenURI = baseURI;
    }

    function setOwnersExplicit(uint256 quantity)
        external
        onlyOwner
        nonReentrant
    {
        _setOwnersExplicit(quantity);
    }

    function numberMinted(address owner) public view returns (uint256) {
        return _numberMinted(owner);
    }

    function getOwnershipData(uint256 tokenId)
        external override
        view
        returns (address _address, uint64 _timestamp)
    {
        TokenOwnership memory ownership = ownershipOf(tokenId);
        return (ownership.addr, ownership.startTimestamp);
    }
}
function mintDnM() external onlyOwner {
    uint256 numChunks = config.dnmAmount / maxBatchSize;
    for (uint256 i = 0; i < numChunks; i++) {
        _safeMint(msg.sender, maxBatchSize);
    }
}

/* DnM Related */