contract PuzlPackDeux is ERC721Enum, Ownable, ReentrancyGuard {
    using Strings for uint256;

    //sale settings
    uint256 constant private MAX_SUPPLY = 10000;
    uint256 constant private MAX_PRESALE_MINT = 5;
    uint256 private TEAM_RESERVE_AVAILABLE_MINTS = 1;
    uint256 private COST = 0.00 ether;
    uint256 private MAX_SALE_MINT = 20;
    address public _trustedContract = 0x0000000000000000000000000000000000000000;

    bool public isPresaleActive = false;
    bool public isSaleActive = false;

    //presale settings
    mapping(address => bool) public presaleWhitelist;
    mapping(address => uint256) public presaleWhitelistMints;

    string private baseURI;

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

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

    function _publicSupply() internal view virtual returns (uint256) {
        return MAX_SUPPLY - TEAM_RESERVE_AVAILABLE_MINTS;
    }

    function _transferNotice(address _from, address _to, uint256 _tokenId) internal {
        if (_trustedContract != address(0)) {
            IWatcher(_trustedContract).watchTransfer(_from, _to, _tokenId);
        }
    }

    // external
    function isWhitelisted (address _address) external view returns (bool) {
        return presaleWhitelist[_address];
    }

    function setTrustedContract(address _contractAddress) public onlyOwner {
        _trustedContract = _contractAddress;
    }

    function flipPresaleState() external onlyOwner {
        isPresaleActive = !isPresaleActive;
    }

    function flipSaleState() external onlyOwner {
        isSaleActive = !isSaleActive;
    }

    function mint(uint256 _mintAmount) external payable {
        require(isSaleActive, "Sale is not active");
        require(_mintAmount > 0, "Minted amount should be positive" );
        require(_mintAmount <= MAX_SALE_MINT, "Minted amount exceeds sale limit" );

        uint256 totalSupply = totalSupply();

        require(totalSupply + _mintAmount <= _publicSupply(), "The requested amount exceeds the remaining supply" );
        require(msg.value >= COST * _mintAmount);

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

    function mintPresale(uint256 _mintAmount) external payable {
        require(isPresaleActive, "Presale is not active");
        require(presaleWhitelist[msg.sender], "Caller is not whitelisted");

        uint256 totalSupply = totalSupply();
        uint256 availableMints = MAX_PRESALE_MINT - presaleWhitelistMints[msg.sender];

        require(_mintAmount <= availableMints, "Too many mints requested");
        require(totalSupply + _mintAmount <= _publicSupply(), "The requested amount exceeds the remaining supply");
        require(msg.value >= COST * _mintAmount , "Wrong amount provided");

        presaleWhitelistMints[msg.sender] += _mintAmount;

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

    function withdraw() external onlyOwner {
        require(payable(msg.sender).send(address(this).balance));
    }

    function addToWhitelist(address[] calldata _addresses) external onlyOwner {
        for (uint256 i = 0; i < _addresses.length; i++) {
            require(_addresses[i] != address(0), "Null address is not allowed");
            presaleWhitelist[_addresses[i]] = true;
            presaleWhitelistMints[_addresses[i]] > 0 ? presaleWhitelistMints[_addresses[i]] : 0;
        }
    }

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

    function setMaxMintAmount(uint256 _newMaxMintAmount) external onlyOwner {
        MAX_SALE_MINT = _newMaxMintAmount;
    }

    function tokenURI(uint256 _tokenId) external view virtual override returns (string memory) {
        require(_exists(_tokenId), "ERC721Metadata: Nonexistent token");
        string memory currentBaseURI = _baseURI();
        return bytes(currentBaseURI).length > 0	? string(abi.encodePacked(currentBaseURI, _tokenId.toString(), ".json")) : "";
    }

    // admin minting
     function reserve(address _to, uint256 _reserveAmount) public onlyOwner {
        uint256 supply = totalSupply();
        require(
            _reserveAmount > 0 && _reserveAmount <= TEAM_RESERVE_AVAILABLE_MINTS,
            "Not enough reserve left for team"
        );
        for (uint256 i = 0; i < _reserveAmount; i++) {
            _safeMint(_to, supply + i);
        }
        TEAM_RESERVE_AVAILABLE_MINTS -= _reserveAmount;
    }

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

    function transferFrom(address _from, address _to, uint256 _tokenId) public override {
        ERC721P.transferFrom(_from, _to, _tokenId);
        _transferNotice(_from, _to, _tokenId);
    }

    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory _data) public override {
        ERC721P.safeTransferFrom(_from, _to, _tokenId, _data);
        _transferNotice(_from, _to, _tokenId);
    }
}
function _baseURI() internal view virtual returns (string memory) {
        return baseURI;
    }
// internal