contract BaeCafe is ERC721, Ownable {
    using Counters for Counters.Counter;

    uint256 public immutable MAX_HANA = 3069;

    uint256 public constant TOTAL_RARES = 5;
    uint256 public constant totalWhitelistMint = 2500;
    uint256 public constant totalGiveaways = 405;
    uint256 public constant totalTeamMint = 159;
    uint256 public constant maxBaePurchase = 4;
    uint256 public constant baePrice = 80080000000000000; //0.08008 ETH

    bytes32 public merkleRoot;
    string public baseURI;
    bool public saleIsActive = false;
    uint256 public countTeamMint = 0;
    Counters.Counter public countMintedWhitelist;
    Counters.Counter public countGiveaways;
    Counters.Counter public countRareMint;

    Counters.Counter private _tokenIds;
    mapping(address => uint256) private MINTED_WHITELIST;
    address private openSeaProxyRegistryAddress;

    constructor(
        string memory name_,
        string memory symbol_,
        bytes32 merkleRoot_,
        string memory baseURI_,
        address openSeaProxyRegistryAddress_
    ) Ownable() ERC721(name_, symbol_) {
        merkleRoot = merkleRoot_;
        baseURI = baseURI_;
        openSeaProxyRegistryAddress = openSeaProxyRegistryAddress_;
    }

    /**
     * Function to withdraw
     */
    function withdraw() public onlyOwner {
        Address.sendValue(payable(msg.sender), address(this).balance);
    }

    /**
     * Function return total supply
     */
    function totalSupply() external view returns (uint256) {
        return _tokenIds.current() + countRareMint.current();
    }

    /**
     * Function to set MerkleRoot
     */
    function setMerkleRoot(bytes32 merkleRoot_) public onlyOwner {
        merkleRoot = merkleRoot_;
    }

    /*
     * Pause sale if active, make active if paused
     */
    function flipSaleState() public onlyOwner {
        saleIsActive = !saleIsActive;
    }

    /**
     * Function to set the base URI for all token IDs.
     */
    function setBaseURI(string memory newURI) public onlyOwner {
        baseURI = newURI;
    }

    /**
     * Set some Baes aside
     */
    function teamMint(address to, uint256 amountToMint) public onlyOwner {
        require(
            _tokenIds.current() + amountToMint <= MAX_HANA - TOTAL_RARES,
            "Exceed max supply of Baes"
        );
        require(
            countTeamMint + amountToMint <= totalTeamMint,
            "Exceed max TeamMint"
        );
        countTeamMint += amountToMint;
        for (uint256 i = 1; i <= amountToMint; i++) {
            _mintNFT(to);
        }
    }

    /**
     * Manually mint 1/1 rares afterward.
     */
    function rareMint(address to) public onlyOwner {
        require(countRareMint.current() < TOTAL_RARES, "Exceed max rareMint");
        countRareMint.increment();
        _mint(to, MAX_HANA - countRareMint.current() + 1);
    }

    /**
     * Reward bae to raffle wallet
     */
    function raffleGiveaway(address to) public onlyOwner {
        require(
            _tokenIds.current() < MAX_HANA - TOTAL_RARES,
            "Exceed max supply of Baes"
        );
        require(
            countGiveaways.current() < totalGiveaways,
            "Exceed max giveaways"
        );
        countGiveaways.increment();
        _mintNFT(to);
    }

    /**
     * Get total minted of an address
     */
    function mintedOf(address minter) public view virtual returns (uint256) {
        return MINTED_WHITELIST[minter];
    }

    /**
     * Mints Baes
     */
    function mintWhitelist(
        uint256 availableToMint,
        uint256 amountToMint,
        bytes32[] calldata merkleProof
    ) public payable {
        require(saleIsActive, "Sale must be active to mint Bae");
        require(
            MINTED_WHITELIST[msg.sender] + amountToMint <= maxBaePurchase &&
                MINTED_WHITELIST[msg.sender] + amountToMint <=
                availableToMint &&
                countMintedWhitelist.current() + amountToMint <=
                totalWhitelistMint,
            "Trying to mint too many"
        );
        require(
            baePrice * amountToMint <= msg.value,
            "Ether value sent is not correct"
        );
        require(
            _tokenIds.current() + amountToMint <= MAX_HANA - TOTAL_RARES,
            "Purchase would exceed max supply of Baes"
        );

        // Verify the merkle proof.
        bytes32 node = keccak256(abi.encodePacked(msg.sender, availableToMint));
        require(
            MerkleProof.verify(merkleProof, merkleRoot, node),
            "Invalid Merkle Proof."
        );

        MINTED_WHITELIST[msg.sender] += amountToMint;

        for (uint256 i = 1; i <= amountToMint; i++) {
            countMintedWhitelist.increment();
            _mintNFT(msg.sender);
        }
    }

    function _mintNFT(address to) internal {
        uint256 newTokenId = _tokenIds.current() + 1;
        require(
            newTokenId <= MAX_HANA - TOTAL_RARES,
            "Exceed max supply of Baes"
        );
        _tokenIds.increment();
        _mint(to, newTokenId);
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return baseURI;
    }

    /**
     * @dev Override isApprovedForAll to allowlist user's OpenSea proxy accounts to enable gas-less listings.
     */
    function isApprovedForAll(address owner, address operator)
        public
        view
        override
        returns (bool)
    {
        // Get a reference to OpenSea's proxy registry contract by instantiating
        // the contract using the already existing address.
        ProxyRegistry proxyRegistry = ProxyRegistry(
            openSeaProxyRegistryAddress
        );
        if (address(proxyRegistry.proxies(owner)) == operator) {
            return true;
        }

        return super.isApprovedForAll(owner, operator);
    }
}
function setBaseURI(string memory newURI) public onlyOwner {
    baseURI = newURI;
}

/**
 * Function to set the base URI for all token IDs.
 */