contract ByeBanx is Ownable, ERC721A, ReentrancyGuard {
    using Strings for uint256;
    using ECDSA for bytes32;

    string public baseURI = "ipfs://QmSAo4kt2N9mdgwTF5MREgSWHoF3CxwwmbhZV5M3u83SVg/";
    uint256 public cost = 0.1 ether;
    uint16 public maxSupply = 666;
    uint16 private privateMaxSupply = 660;
    uint public maxMintAmount = 2;
    bool public publicSale = false;
    bool public privateSale = false;
    address public signer1;
    address public signer2;
    address public signer3;
    address public signer4;


    constructor() ERC721A ("Bye BanX", "ByeBanX", 6, maxSupply){}

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

    function mint(uint _mintAmount) external payable {
        require(publicSale, "Minting currently on hold");
        require(_mintAmount <= maxMintAmount, "Maximum mint amount exceeded");
        require(totalSupply() + _mintAmount <= maxSupply, "Purchase would exceed max supply of NFT");
        mintX(_mintAmount);
    }

// TODO: edit to support second group whitelist to mint 4 
    function privateSaleMint(uint _mintAmount, bytes memory signature) external payable {
        require(privateSale, "Private sale is not currently running");
        uint8 group = 0;
        address recover = recoverSignerAddress(msg.sender, signature);
        if (recover == signer1) {
            group = 1;
        }else if (recover == signer2) {
            group = 2;
        }else if (recover == signer3) {
            group = 3;
        }else if (recover == signer4) {
            group = 4;
        }

        require(group > 0 && group <= 4, "Address not whitelisted for sale");
        require(totalSupply() + _mintAmount <= privateMaxSupply, "Purchase would exceed max supply of private sale");
        require(balanceOf(msg.sender) + _mintAmount <= group, "Minting would exceed max per wallet for presale");
        mintX(_mintAmount);
    }

    function mintX(uint _mintAmount) private {
        require(_mintAmount > 0, "Amount to mint can not be 0");
        if (msg.sender != owner()) {
            require(msg.value >= cost * _mintAmount, "Amount sent less than the cost of minting NFT(s)");
        }
        _safeMint(msg.sender, _mintAmount);
    }

    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: tokenURI queried for nonexistent token");
        string memory currentBaseURI = _baseURI();
        return bytes(currentBaseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString(), ".json")) : "";
    }

    //
    function hashTransaction(address minter) private pure returns (bytes32) {
        bytes32 argsHash = keccak256(abi.encodePacked(minter));
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", argsHash));
    }

    function recoverSignerAddress(address minter, bytes memory signature) private pure returns (address) {
        bytes32 hash = hashTransaction(minter);
        return hash.recover(signature);
    }


    // Access control

    function devMint(uint _mintAmount) external onlyOwner() {
        require(totalSupply() + _mintAmount <= maxSupply, "Purchase would exceed max supply of NFT");
        mintX(_mintAmount);
    }

    function setSigner(address _signer) public onlyOwner() {
        signer1 = _signer;
    }

    function setSigner2(address _signer) public onlyOwner() {
        signer2 = _signer;
    }

    function setSigner3(address _signer) public onlyOwner() {
        signer3 = _signer;
    }

    function setSigner4(address _signer) public onlyOwner() {
        signer4 = _signer;
    }

    function setCost(uint256 _cost) public onlyOwner() {
        cost = _cost;
    }

    function setMaxMintAmount(uint256 _maxMintAmount) public onlyOwner() {
        maxMintAmount = _maxMintAmount;
    }

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

    function togglePublicSale() public onlyOwner() {
        publicSale = !publicSale;
    }

    function togglePrivateSale() public onlyOwner() {
        privateSale = !privateSale;
    }

    function configure(bool _publicSale, bool _privateSale, uint256 _maxMintAmount,
        address _signer1, address _signer2, address _signer3, address _signer4) public onlyOwner() {
        publicSale = _publicSale;
        privateSale = _privateSale;
        maxMintAmount = _maxMintAmount;
        signer1 = _signer1;
        signer2 = _signer2;
        signer3 = _signer3;
        signer4 = _signer4;
    }

    function withdraw() public payable onlyOwner() {
        (bool success,) = address(msg.sender).call{value : address(this).balance}("");
        require(success, "Unable to withdraw balance");
    }

}
function devMint(uint _mintAmount) external onlyOwner() {
    require(totalSupply() + _mintAmount <= maxSupply, "Purchase would exceed max supply of NFT");
    mintX(_mintAmount);
}

// Access control