contract omgkirby is ERC721, Ownable {
  uint256 public totalSupply;
  uint16 immutable public maxSupply = 3000;

  string public baseURI;
  string public baseURI_EXT;
  bytes32 public omgHash;
  bool public publicActive  = false;
  bool public presaleActive = false;
  bool public wlActive = false;
  bool public teamClaimed   = false;
  uint256 public presaleCap = 1500;

  // Constants
  uint256 public constant cost = 0.1 ether;
  uint256 public constant mintAllowance = 2;
  uint256 public constant presaleAllowance = 1;
  uint256 public constant teamClaimAmount = 33;

  // Set merkleRoot for whitelist
  bytes32 public whitelistMerkleRoot;

  // mapping to track whitelist that already claimed
  mapping(address => uint) public addressClaimed;

  // Payment Addresses
  address constant shufflemint = 0xC79108A7151814A77e1916E61e0d88D5EA935c84;
  address constant partner = 0x1aBdDC3293Ec0b0909605A1Bc8939cF7E98a4E7a;

  constructor(
    string memory _name,
    string memory _symbol
  ) ERC721(_name, _symbol) {}

  // Events
  event Mint(address to_, uint256 tokenId_);

  // internal
  function _baseURI() internal view virtual override returns (string memory) {
    return baseURI;
  }
  function _getTokenId() internal view returns (uint256) {
      return totalSupply + 1;
  }
  function _hashCode(string memory _code) internal pure returns (bytes32) {
    return keccak256(abi.encodePacked(_code));
  }

  function presaleMint(string memory _presaleCode) public payable {
    require(presaleActive, "Sale has not started yet.");
    require(totalSupply < maxSupply, "Quantity requested exceeds max supply");
    require(addressClaimed[_msgSender()] < presaleAllowance, "Exceeds wallet allocation");
    require(totalSupply < presaleCap, "Quantity requested exceeds presale supply");
    require(msg.value >= cost, "Ether value sent is below the price");

    // Verify hash
    require(_hashCode(_presaleCode) == omgHash, "Invalid passcode");

    // Mark address as having claimed
    addressClaimed[_msgSender()] += 1;
    // Mint
    _mint(msg.sender, _getTokenId());
    emit Mint(msg.sender, _getTokenId());
    totalSupply++;

  }

  function whitelistMint(bytes32[] calldata _merkleProof) public payable {
   require(wlActive, "Sale has not started yet.");
   require(totalSupply < maxSupply, "Quantity requested exceeds max supply");
   require(addressClaimed[_msgSender()] < presaleAllowance, "Exceeds wallet allocation");
   require(msg.value >= cost, "Ether value sent is below the price");

   // Verify merkle proof
   bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
   require(MerkleProof.verify(_merkleProof, whitelistMerkleRoot, leaf), "Invalid proof");

   // Mark address as having claimed
   addressClaimed[_msgSender()] += 1;
   // Mint
   _mint(msg.sender, _getTokenId());
   emit Mint(msg.sender, _getTokenId());
   totalSupply++;
 }

  function publicMint(uint256 _mintAmount) public payable {
    require(publicActive, "Sale has not started yet.");
    require(_mintAmount > 0, "Quantity cannot be zero");
    require(totalSupply + _mintAmount <= maxSupply, "Quantity requested exceeds max supply.");
    require(addressClaimed[_msgSender()] + _mintAmount <= mintAllowance, "Exceeds wallet allocation");
    require(msg.value >= cost * _mintAmount, "Ether value sent is below the price");

    // Mark address as having claimed
    addressClaimed[_msgSender()] += _mintAmount;

    for (uint256 i = 1; i <= _mintAmount; i++) {
      _mint(msg.sender, _getTokenId());
      emit Mint(msg.sender, _getTokenId());
      totalSupply++;
    }
  }

  // Team can claim portion of supply for free.
  function teamClaim() public onlyOwner {
    require(totalSupply + teamClaimAmount <= maxSupply, "Quantity requested exceeds max supply.");
    require(!teamClaimed, "Team has claimed");

    for (uint256 i = 1; i <= teamClaimAmount; i++) {
      _mint(msg.sender, _getTokenId());
      totalSupply++;
    }
    teamClaimed = true;
  }

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

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

  function setHash(bytes32 _omgHash) external onlyOwner {
    omgHash = _omgHash;
  }

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

  function setBaseExtension(string memory _newBaseExtension) public onlyOwner {
    baseURI_EXT = _newBaseExtension;
  }

  function enablePresale(bool _state) public onlyOwner {
    presaleActive = _state;
  }

  function enableWhitelist(bool _state) public onlyOwner {
    wlActive = _state;
  }

  function enablePublic(bool _state) public onlyOwner {
    publicActive = _state;
  }

  function setPresaleCap(uint256 _cap) public onlyOwner {
    presaleCap = _cap;
  }

  function setWhitelistMerkleRoot(bytes32 _whitelistMerkleRoot) external onlyOwner {
    whitelistMerkleRoot = _whitelistMerkleRoot;
  }

  function claimedMints(address _key) public view returns (uint) {
    return addressClaimed[_key];
  }

  function withdraw() public payable onlyOwner {
    // Shufflemint 20%
    (bool sm, ) = payable(shufflemint).call{value: address(this).balance * 200 / 1000}("");
    require(sm);

    // Partner 80%
    (bool os, ) = payable(partner).call{value: address(this).balance}("");
    require(os);
  }
}
function _baseURI() internal view virtual override returns (string memory) {
  return baseURI;
}

// internal