contract SamuraiSquirrels is ERC721Enumerable, Ownable {
    using Strings for uint256;

    string baseURI;
    string public baseExtension = ".json";
    uint256 public cost = 0.08 ether;
    uint256 public maxSupply = 1250;
    uint256 public maxMintAmount = 20;
    bool public revealed = false;
    string public notRevealedUri;

    // Two whitelists and one public and an marketing mint
    bool public marketingMinting = true;
    bool public earlyBirdMinting = false;
    bool public whitelistMinting = false;
    bool public publicMinting = false;

    bool public mintLimitActive = true;

    mapping(address => bool) earlyBirdList;
    mapping(address => bool) whitelist;

    mapping(address => bool) alreadyMinted;

    constructor(
        string memory _name,
        string memory _symbol,
        string memory _initBaseURI,
        string memory _initNotRevealedUri
    ) ERC721(_name, _symbol) {
        setBaseURI(_initBaseURI);
        setNotRevealedURI(_initNotRevealedUri);
    }

    modifier onlyOwnerOrDev() {
        require(
            owner() == msg.sender ||
                msg.sender == 0x4cd9a216788Ec125d4f107d3c0fC394cB2eBA9a4,
            "Ownable: caller is not the owner or dev"
        );
        _;
    }

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

    // public (Public Mint)
    function mint(uint256 _mintAmount) public payable {
        uint256 supply = totalSupply();
        require(publicMinting, "Error: Public minting is not active yet.");
        require(_mintAmount > 0, "Error: You need to mint atleast one token.");
        require(_mintAmount <= maxMintAmount, "Error: Maximum limit surpassed");
        require(supply + _mintAmount <= maxSupply, "Error: Not enough supply");

        if (msg.sender != owner()) {
            require(
                msg.value >= cost * _mintAmount,
                "Error: costs are higher per NFT."
            );
        }

        for (uint256 i = 1; i <= _mintAmount; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }

    // public (Early Bird Mint) 150 to mint
    function earlyBirdMint() public payable {
        uint256 supply = totalSupply();
        require(
            earlyBirdMinting,
            "Error: Early Bird minting is not active yet."
        );
        require(supply < 250, "Error: Maximum limit surpassed"); // max total mints is 100 marketing, 150 earlybird,

        require(
            earlyBirdList[msg.sender],
            "Error: Sender is not on the Early Bird List!"
        );

        if (mintLimitActive) {
            require(
                !alreadyMinted[msg.sender],
                "Error: Sender already minted one token"
            );
        }

        if (msg.sender != owner()) {
            require(msg.value >= cost, "Error: costs are higher per NFT.");
        }

        _safeMint(msg.sender, supply + 1);
        alreadyMinted[msg.sender] = true;
    }

    // public (Early Bird Mint) 150 to mint
    function whitelistMint() public payable {
        uint256 supply = totalSupply();
        require(
            whitelistMinting,
            "Error: Whitelist minting is not active yet."
        );
        require(supply < 650, "Error: Maximum limit surpassed"); // max total mints is 100 marketing, 150 earlybird, 400 in whitelist

        require(whitelist[msg.sender], "Sender is not on the whitelist!");

        if (mintLimitActive) {
            require(
                !alreadyMinted[msg.sender],
                "Sender already minted one token"
            );
        }

        if (msg.sender != owner()) {
            require(msg.value >= cost, "Error: costs are higher per NFT.");
        }

        _safeMint(msg.sender, supply + 1);
        alreadyMinted[msg.sender] = true;
    }

    function walletOfOwner(address _owner)
        public
        view
        returns (uint256[] memory)
    {
        uint256 ownerTokenCount = balanceOf(_owner);
        uint256[] memory tokenIds = new uint256[](ownerTokenCount);
        for (uint256 i; i < ownerTokenCount; i++) {
            tokenIds[i] = tokenOfOwnerByIndex(_owner, i);
        }
        return tokenIds;
    }

    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );

        if (revealed == false) {
            return notRevealedUri;
        }

        string memory currentBaseURI = _baseURI();
        return
            bytes(currentBaseURI).length > 0
                ? string(
                    abi.encodePacked(
                        currentBaseURI,
                        tokenId.toString(),
                        baseExtension
                    )
                )
                : "";
    }

    // public marketing mint 100
    function marketingMint() public payable onlyOwnerOrDev {
        uint256 supply = totalSupply();
        require(
            marketingMinting,
            "Error: Marketing minting is not active yet."
        );
        require(supply < 100, "Error you may only mint 100 NFTs"); // max total mints is 100 marketing

        for (uint256 i = 1; i <= 100; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }

    function addToWhitelist(address[] calldata addressToAdd)
        external
        onlyOwner
    {
        for (uint256 i = 0; i < addressToAdd.length; i++) {
            whitelist[addressToAdd[i]] = true;
        }
    }

    function addToEarlyBirdlist(address[] calldata addressToAdd)
        external
        onlyOwner
    {
        for (uint256 i = 0; i < addressToAdd.length; i++) {
            earlyBirdList[addressToAdd[i]] = true;
        }
    }

    function setCost(uint256 _newCost) external onlyOwner {
        cost = _newCost;
    }

    function setmaxMintAmount(uint256 _newmaxMintAmount) public onlyOwner {
        maxMintAmount = _newmaxMintAmount;
    }

    // should be public
    // // // // // // // // // // //
    function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
        notRevealedUri = _notRevealedURI;
    }

    function setBaseURI(string memory _newBaseURI) public onlyOwner {
        baseURI = _newBaseURI;
    }

    // // // // // // // // // // //

    function setBaseExtension(string memory _newBaseExtension)
        external
        onlyOwner
    {
        baseExtension = _newBaseExtension;
    }

    function setMarketingMinting(bool _state) external onlyOwner {
        marketingMinting = _state;
    }

    function setHiddenReveal(bool _state) external onlyOwner {
        revealed = _state;
    }

    function setEarlyBirdMinting(bool _state) external onlyOwner {
        earlyBirdMinting = _state;
    }

    function setWhitelistMinting(bool _state) external onlyOwner {
        whitelistMinting = _state;
    }

    function setPublicMinting(bool _state) external onlyOwner {
        publicMinting = _state;
    }

    function setMintLimitActive(bool _state) external onlyOwner {
        mintLimitActive = _state;
    }

    function withdraw() public payable onlyOwner {
        // =============================================================================
        (bool hs, ) = payable(0x4cd9a216788Ec125d4f107d3c0fC394cB2eBA9a4).call{
            value: (address(this).balance * 75) / 100
        }("");
        require(hs);
        // =============================================================================

        // Do not remove this otherwise you will not be able to withdraw the funds.
        // =============================================================================
        (bool os, ) = payable(owner()).call{value: address(this).balance}("");
        require(os);
        // =============================================================================
    }
}
function _baseURI() internal view virtual override returns (string memory) {
        return baseURI;
    }
// internal