// Contract: CrowdfundingWithReferral
// Target function: function _withdrawCommissions() internal
    function getTotalCommission() public view returns (uint256) {
        uint256 totalCommission = 0;
        for (uint256 i = 0; i < commissionRates.length; i++) {
            totalCommission += commissionRates[i];
        }
        return totalCommission;
    }

    // Retrieves user details for a given address.
    function getUserDetails(
        address userAddress
    ) public view returns (User memory) {
        return users[userAddress];
    }

    // Determines the current vesting phase based on the timestamp.
    function getVestingPhase() public view returns (uint256) {
        if (vestingInterval > 0 && successTimestamp > 0) {
            uint256 vestingPhase = 0;
            if (block.timestamp >= successTimestamp + 4 * vestingInterval) {
                vestingPhase = 4;
            } else if (
                block.timestamp >= successTimestamp + 3 * vestingInterval
            ) {
                vestingPhase = 3;
            } else if (
                block.timestamp >= successTimestamp + 2 * vestingInterval
            ) {
                vestingPhase = 2;
            } else if (
                block.timestamp >= successTimestamp + 1 * vestingInterval
            ) {
                vestingPhase = 1;
            }
            return vestingPhase;
        } else return 0;
    }

    /////////////////////////////// SETTERS ///////////////////////////////

    // Sets the referrer for a specific contributor.
    function setReferrer(address contributor, address referrer) internal {
        require(referrer != contributor, "Cannot refer self");
        users[contributor].referrer = referrer;
    }

    // Sets airdrop amounts for a list of addresses.
    function setAirdropList(
        address[] calldata airdropList,
        uint256 airdropAmount
    ) external onlyOwner {
        require(!isPresaleSuccess && !isPresaleCancelled, "Presale is ended");
        require(
            airdropList.length < 251,
            "GAS Error: max airdrop limit is 251 addresses"
        );

        for (uint256 i = 0; i < airdropList.length; i++) {
            users[airdropList[i]].totalAirdrop += airdropAmount;
        }

        globalTotalAirdrop += airdropList.length * airdropAmount;
    }

    // Sets airdrop amount for an individual address.
    function setUserAirdrop(
        address userAddress,
        uint256 airdropAmount
    ) external onlyOwner {
        require(!isPresaleSuccess && !isPresaleCancelled, "Presale is ended");
        globalTotalAirdrop -= users[userAddress].totalAirdrop;
        users[userAddress].totalAirdrop = airdropAmount;
        globalTotalAirdrop += airdropAmount;
    }

    // Sets the referral depth and corresponding commission rates.
    function setReferral(
        uint256 _referralDepth,
        uint256[] calldata _commissionRates
    ) external onlyOwner {
        require(!isPresaleSuccess && !isPresaleCancelled, "Presale is ended");
        require(
            _commissionRates.length == _referralDepth,
            "Rates must have same depth"
        );
        referralDepth = _referralDepth;
        commissionRates = _commissionRates;
    }

    // Function to set vesting interval
    function setVestingInterval(uint256 _vestingInterval) external onlyOwner {
        require(!isPresaleSuccess && !isPresaleCancelled, "Presale is ended");
        vestingInterval = _vestingInterval;
    }

    // Function to set presale success
    function setPresaleSuccess(bool _isPresaleSuccess) external onlyOwner {
        require(!isPresaleSuccess && !isPresaleCancelled, "Presale is ended");
        if (_isPresaleSuccess) {
            isPresaleSuccess = true;
            successTimestamp = block.timestamp;
        } else {
            isPresaleCancelled = true;
        }
        isPresaleOpened = false;
    }

    // Function to set presale opened
    function setPresaleOpened() external onlyOwner {
        require(!isPresaleSuccess && !isPresaleCancelled, "Presale is ended");
        require(!isPresaleOpened, "Presale is already opened");
        isPresaleOpened = true;
        _startNewRound();
    }

    // Function to set commission DBTT
    function setCommissionDBTT(bool _isCommissionDBTT) external onlyOwner {
        require(!isPresaleSuccess && !isPresaleCancelled, "Presale is ended");
        isCommissionDBTT = _isCommissionDBTT;
    }

    function setPrice(uint256 _priceUSDTRate) external onlyOwner {
        require(!isPresaleSuccess && !isPresaleCancelled, "Presale is ended");
        priceUSDTRate = _priceUSDTRate;
    }

    // Function to set min USDT contribution
    function setMinUSDTContribution(
        uint256 _minUSDTContribution
    ) external onlyOwner {
        require(!isPresaleSuccess && !isPresaleCancelled, "Presale is ended");
        minUSDTContribution = _minUSDTContribution;
    }

    // Function to set min ETH contribution
    function setMinETHContribution(
        uint256 _minETHContribution
    ) external onlyOwner {
        require(!isPresaleSuccess && !isPresaleCancelled, "Presale is ended");
        minETHContribution = _minETHContribution;
    }

    // Function to set max DBTT allocation
    function setMaxAllocationDBTT(uint256 _maxAllocationDBTT) public onlyOwner {
        require(!isPresaleSuccess && !isPresaleCancelled, "Presale is ended");
        maxDBTTAllocation = _maxAllocationDBTT;
    }

    // Remove contribution limits
    function setRemoveContributionLimits() external onlyOwner {
        require(!isPresaleSuccess && !isPresaleCancelled, "Presale is ended");
        minUSDTContribution = 0;
        minETHContribution = 0;
        maxDBTTAllocation = 0;
    }

    function setRoundSaleCapDBTT(
        uint256 _round,
        uint256 _saleCapDBTT
    ) external onlyOwner {
        require(
            !isPresaleOpened && !isPresaleSuccess && !isPresaleCancelled,
            "Presale is already started"
        );
        require(_round > 0, "Round must be greater than 0");
        contributionRound[_round - 1].saleCapDBTT = _saleCapDBTT * 10 ** 18;
    }

    function setRoundPriceUSDTRate(
        uint256 _round,
        uint256 _priceUSDTRate
    ) external onlyOwner {
        require(
            !isPresaleOpened && !isPresaleSuccess && !isPresaleCancelled,
            "Presale is already started"
        );
        require(_round > 0, "Round must be greater than 0");
        contributionRound[_round - 1].priceUSDTRate = _priceUSDTRate;
    }

    function setServiceFee(
        uint256 _serviceFee,
        address _serviceFeeReceiver
    ) external onlyOwner {
        require(
            !isPresaleSuccess && !isPresaleCancelled,
            "Presale is already started"
        );
        serviceFee = _serviceFee;
        serviceFeeReceiver = _serviceFeeReceiver;
    }

    function setLaunchPrice(uint256 _launchPriceUSDTRate) external onlyOwner {
        require(_launchPriceUSDTRate > 0, "Price must be greater than 0");
        launchPriceUSDTRate = _launchPriceUSDTRate;
    }

    function _startNewRound() internal {
        require(totalContributionRounds > 0, "No round exists yet!");

        if (purchasedDBTTRound > 0) {
            purchasedDBTTRound = 0;
        }

        priceUSDTRate = contributionRound[currentRound].priceUSDTRate;
        saleCapDBTT = contributionRound[currentRound].saleCapDBTT;
        if (contributionRound[currentRound + 1].priceUSDTRate > 0) {
            nextPriceUSDTRate = contributionRound[currentRound + 1]
                .priceUSDTRate;
        } else {
            nextPriceUSDTRate = 0;
        }

        currentRound++;
    }

    function addNewRound(
        uint256 _saleCapDBTT,
        uint256 _priceUSDTRate
    ) external onlyOwner {
        require(
            !isPresaleOpened && !isPresaleSuccess && !isPresaleCancelled,
            "Presale is already started"
        );
        require(
            _saleCapDBTT > 0 && _priceUSDTRate > 0,
            "Sale cap and price must be greater than 0"
        );

        totalContributionRounds++;
        contributionRound[totalContributionRounds - 1].saleCapDBTT =
            _saleCapDBTT *
            10 ** 18;
        contributionRound[totalContributionRounds - 1]
            .priceUSDTRate = _priceUSDTRate;
    }

    /////////////////////////////// MAIN ///////////////////////////////

    function contributeWithETH(address referrer) external payable nonReentrant {
        require(!isPresaleSuccess && !isPresaleCancelled, "Presale is ended");
        require(isPresaleOpened, "Presale is closed");

        if (minETHContribution > 0) {
            require(
                msg.value >= minETHContribution,
                "Amount must be greater than min contribution"
            );
        } else {
            require(msg.value > 1000, "Amount must be greater than 1000 wei");
        }

        require(msg.value > 0, "Amount must be greater than 0");
        weth.deposit{value: msg.value}();

        users[msg.sender].totalContributionETH += msg.value;

        if (purchasedDBTTRound == saleCapDBTT) {
            if (currentRound < totalContributionRounds) {
                _startNewRound();
            } else {
                revert("Sale cap reached");
            }
        }

        uint256 allocation = ((msg.value * getETHPrice()) / priceUSDTRate) /
            10 ** 2;

        // maybe not here
        if (maxDBTTAllocation > 0) {
            require(
                users[msg.sender].totalPurchasedTokens + allocation <=
                    maxDBTTAllocation,
                "Amount must be less than max allocation"
            );
        }

        if (purchasedDBTTRound + allocation > saleCapDBTT) {
            if (currentRound < totalContributionRounds) {
                uint256 currentRoundDBTT = saleCapDBTT - purchasedDBTTRound;
                uint256 getSpent = (currentRoundDBTT * priceUSDTRate) /
                    10 ** 18;
                uint256 getUSDTInitial = (msg.value * getETHPrice()) / 10 ** 20;
                _startNewRound();
                uint256 remainingDBTT = ((getUSDTInitial - getSpent) *
                    10 ** 18) / priceUSDTRate;
                allocation = currentRoundDBTT + remainingDBTT;
                purchasedDBTTRound += remainingDBTT;
                require(purchasedDBTTRound <= saleCapDBTT, "Sale cap reached");
            } else {
                revert("Sale cap reached");
            }
        } else if (purchasedDBTTRound + allocation == saleCapDBTT) {
            if (currentRound < totalContributionRounds) {
                _startNewRound();
            } else {
                purchasedDBTTRound += allocation;
            }
        } else {
            purchasedDBTTRound += allocation;
        }

        users[msg.sender].totalPurchasedTokens += allocation;
        totalPurchasedDBTT += allocation;
        totalRaisedETH += msg.value;

        if (referrer != address(0)) {
            setReferrer(msg.sender, referrer);
        }

        // Distribute commissions (if applicable)
        if (referralDepth > 0) {
            distributeCommissions(msg.sender, WETH, msg.value);
        }

        emit NewContribution(msg.sender, msg.value, referrer);
    }

    // Function to contribute with ERC20 tokens
    function contributeWithToken(
        address token,
        uint256 amount,
        address referrer
    ) external nonReentrant {
        require(!isPresaleSuccess && !isPresaleCancelled, "Presale is ended");
        require(isPresaleOpened, "Presale is closed");

        if (minUSDTContribution > 0 && token == USDT) {
            require(
                amount >= minUSDTContribution,
                "Amount must be greater than min contribution"
            );
        } else if (token == USDT) {
            require(amount > 1000, "Amount must be greater than 1000 wei");
        }
        if (minETHContribution > 0 && token == WETH) {
            require(
                amount >= minETHContribution,
                "Amount must be greater than min contribution"
            );
        } else if (token == WETH) {
            require(amount > 1000, "Amount must be greater than 1000 wei");
        }
        require(amount > 0, "Amount must be greater than 0");
        require(
            IERC20(token).allowance(msg.sender, address(this)) >= amount,
            "Token allowance too low"
        );
        require(token == USDT || token == WETH, "ERC20 token not valid");

        if (token == USDT) {
            // Transfer tokens to this contract
            IERC20USDT(token).transferFrom(
                msg.sender,
                address(this),
                amount
            );
        } else {
            // Transfer tokens to this contract
            bool sent = IERC20(token).transferFrom(
                msg.sender,
                address(this),
                amount
            );
            require(sent, "Token transfer failed");
        }

        if (purchasedDBTTRound == saleCapDBTT) {
            if (currentRound < totalContributionRounds) {
                _startNewRound();
            } else {
                revert("Sale cap reached");
            }
        }

        uint256 allocation;
        if (token == USDT) {
            users[msg.sender].totalContributionUSDT += amount;
            allocation = (amount * 10 ** (18)) / priceUSDTRate;
            totalRaisedUSDT += amount;
        } else {
            users[msg.sender].totalContributionETH += amount;
            allocation = ((amount * getETHPrice()) / priceUSDTRate) / 10 ** 2;
            totalRaisedETH += amount;
        }

        if (maxDBTTAllocation > 0) {
            require(
                users[msg.sender].totalPurchasedTokens + allocation <=
                    maxDBTTAllocation,
                "Amount must be less than max allocation"
            );
        }

        if (purchasedDBTTRound + allocation > saleCapDBTT) {
            if (currentRound < totalContributionRounds) {
                uint256 currentRoundDBTT = saleCapDBTT - purchasedDBTTRound;
                uint256 getSpent = (currentRoundDBTT * priceUSDTRate) /
                    10 ** 18;
                uint256 getUSDTInitial;
                if (token == USDT) {
                    getUSDTInitial = amount;
                } else {
                    getUSDTInitial = (amount * getETHPrice()) / 10 ** 20;
                }
                _startNewRound();
                uint256 remainingDBTT = ((getUSDTInitial - getSpent) *
                    10 ** 18) / priceUSDTRate;
                allocation = currentRoundDBTT + remainingDBTT;
                purchasedDBTTRound += remainingDBTT;
                require(purchasedDBTTRound <= saleCapDBTT, "Sale cap reached");
            } else {
                revert("Sale cap reached");
            }
        } else if (purchasedDBTTRound + allocation == saleCapDBTT) {
            if (currentRound < totalContributionRounds) {
                _startNewRound();
            } else {
                purchasedDBTTRound += allocation;
            }
        } else {
            purchasedDBTTRound += allocation;
        }

        users[msg.sender].totalPurchasedTokens += allocation;
        totalPurchasedDBTT += allocation;

        if (referrer != address(0)) {
            setReferrer(msg.sender, referrer);
        }

        // Distribute commissions (if applicable)
        if (referralDepth > 0) {
            distributeCommissions(msg.sender, token, amount);
        }

        emit NewContribution(msg.sender, amount, referrer);
    }

    // Internal function to handle commission distribution
    function distributeCommissions(
        address contributor,
        address token,
        uint256 amount
    ) internal {
        address currentReferrer = users[contributor].referrer;

        for (uint256 i = 0; i < referralDepth; i++) {
            if (currentReferrer == address(0)) {
                break;
            }

            if (currentReferrer == contributor) {
                break;
            }

            uint256 commission = (amount * commissionRates[i]) / 1000;

            if (isCommissionDBTT && token == USDT) {
                uint256 commissionDBTT = (commission * 10 ** 18) /
                    priceUSDTRate;
                users[currentReferrer].totalCommissionDBTT += commissionDBTT;
                globalCommissionDBTT += commissionDBTT;
            } else if (isCommissionDBTT) {
                uint256 commissionDBTT = (commission * getETHPrice()) /
                    priceUSDTRate /
                    10 ** 2;
                users[currentReferrer].totalCommissionDBTT += commissionDBTT;
                globalCommissionDBTT += commissionDBTT;
            } else if (token == USDT) {
                users[currentReferrer].totalCommissionUSDT += commission;
                globalCommissionUSDT += commission;
            } else {
                users[currentReferrer].totalCommissionETH += commission;
                globalCommissionETH += commission;
            }

            // Move to the next referrer
            currentReferrer = users[currentReferrer].referrer;
        }
    }

    // Function to withdraw commissions
    function withdrawCommissions() external nonReentrant {
        require(isPresaleSuccess, "Claim not active");
        require(
            users[msg.sender].totalCommissionUSDT > 0 ||
                users[msg.sender].totalCommissionETH > 0 ||
                users[msg.sender].totalCommissionDBTT > 0,
            "No commissions to claim"
        );

        _withdrawCommissions();
    }