// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Governor} from "@openzeppelin-contracts/governance/Governor.sol";
import {GovernorPreventLateQuorum} from "@openzeppelin-contracts/governance/extensions/GovernorPreventLateQuorum.sol";
import {GovernorSettings} from "@openzeppelin-contracts/governance/extensions/GovernorSettings.sol";
import {GovernorStorage} from "@openzeppelin-contracts/governance/extensions/GovernorStorage.sol";
import {GovernorVotes} from "@openzeppelin-contracts/governance/extensions/GovernorVotes.sol";
import {GovernorVotesQuorumFraction} from "@openzeppelin-contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
import {IVotes} from "@openzeppelin-contracts/governance/utils/IVotes.sol";
import {ReentrancyGuard} from "@openzeppelin-contracts/utils/ReentrancyGuard.sol";

import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";

import {GovernorGhostCounting} from "./GovernorGhostCounting.sol";
import {Babylonian} from "../libraries/FixedPoint.sol";

contract GhostGovernor is
    GovernorGhostCounting,
    GovernorPreventLateQuorum,
    GovernorSettings,
    GovernorStorage,
    GovernorVotes,
    GovernorVotesQuorumFraction,
    ReentrancyGuard
{
    using SafeERC20 for IERC20;

    uint256 public activeProposedLock;
    uint256 private _lastProposalId;
    mapping(uint256 => uint256) public lockedAmounts;

    error ProposerNotEnoughVotes(uint256 proposalVotes, uint256 accumulatedVotes);
    error InsufficientFundsToExtend(uint256 votes, uint256 neededVotes);

    constructor(
        IVotes _ghst,
        uint48 _initialVoteExtension,
        uint48 _initialVotingDelay,
        uint32 _initialVotingPeriod,
        uint256 _initialProposalThreshold,
        uint256 _quorumFraction
    )
        Governor("GhostGovernorV1")
        GovernorVotes(_ghst)
        GovernorPreventLateQuorum(_initialVoteExtension)
        GovernorSettings(_initialVotingDelay, _initialVotingPeriod, _initialProposalThreshold)
        GovernorVotesQuorumFraction(_quorumFraction)
    {}

    function proposalThreshold()
        public
        view
        virtual
        override(Governor, GovernorSettings)
        returns (uint256)
    {
        return super.proposalThreshold();
    }

    function proposalDeadline(uint256 proposalId)
        public
        view
        virtual
        override(Governor, GovernorPreventLateQuorum)
        returns (uint256)
    {
        return super.proposalDeadline(proposalId);
    }

    function releaseLocked(uint256 proposalId) public nonReentrant returns (uint256) {
        uint256 releaseAmount = lockedAmounts[proposalId];
        address proposer = proposalProposer(proposalId);

        bytes32 currentProposalState = _encodeStateBitmap(state(proposalId));
        bytes32 activePendingMask =
            _encodeStateBitmap(ProposalState.Pending) |
            _encodeStateBitmap(ProposalState.Active);

        if (releaseAmount > 0 && (currentProposalState & activePendingMask == 0)) {
            lockedAmounts[proposalId] = 0;
            IERC20(address(token())).safeTransfer(proposer, releaseAmount);
            return releaseAmount;
        }

        return 0;
    }

    function state(uint256 proposalId) public view virtual override returns (ProposalState) {
        ProposalState result = super.state(proposalId);

        if (result == ProposalState.Active) {
            (uint256 againstVotes, uint256 forVotes,) = proposalVotes(proposalId);
            uint256 halfTotalVotes = token().getPastTotalSupply(proposalSnapshot(proposalId)) / 2;
            if (forVotes > halfTotalVotes) return ProposalState.Succeeded;
            if (againstVotes > halfTotalVotes) return ProposalState.Defeated;
        }

        return result;
    }

    function propose(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        string memory description
    ) public virtual override returns (uint256) {
        address proposer = _msgSender();

        // check description restriction
        if (!_isValidDescriptionForProposer(proposer, description)) {
            revert GovernorRestrictedProposer(proposer);
        }

        // check proposal threshold
        uint256 proposerVotes = getVotes(proposer, clock() - 1);
        uint256 votesThreshold = proposalThreshold();
        if (proposerVotes < votesThreshold) {
            revert GovernorInsufficientProposerVotes(proposer, proposerVotes, votesThreshold);
        }

        uint256 proposalId = _lastProposalId;
        if (proposalId != 0) {
            bytes32 currentProposalState = _encodeStateBitmap(state(proposalId));
            bytes32 activePendingMask =
                _encodeStateBitmap(ProposalState.Pending) |
                _encodeStateBitmap(ProposalState.Active);

            if (currentProposalState & activePendingMask != 0) {
                if (proposerVotes <= activeProposedLock) {
                    revert ProposerNotEnoughVotes(proposerVotes, activeProposedLock);
                }
                (
                    address[] memory currentTargets,
                    uint256[] memory currentValues,
                    bytes[] memory currentCalldatas,
                    bytes32 descriptionHash
                ) = proposalDetails(proposalId);
                _cancel(currentTargets, currentValues, currentCalldatas, descriptionHash);
            }
        }

        proposalId = _propose(targets, values, calldatas, description, proposer);
        _countVote(proposalId, proposer, 1, proposerVotes, "");

        lockedAmounts[proposalId] += proposerVotes;
        activeProposedLock = proposerVotes;
        _lastProposalId = proposalId;
        IERC20(address(token())).safeTransferFrom(proposer, address(this), proposerVotes);

        return proposalId;
    }

    function _propose(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        string memory description,
        address proposer
    ) internal virtual override(Governor, GovernorStorage) returns (uint256) {
        return super._propose(targets, values, calldatas, description, proposer);
    }

    function _castVote(
        uint256 proposalId,
        address account,
        uint8 support,
        string memory reason,
        bytes memory params
    ) internal virtual override(Governor, GovernorPreventLateQuorum) returns (uint256) {
        return super._castVote(proposalId, account, support, reason, params);
    }

    function _voteSucceeded(uint256 proposalId)
        internal
        view
        virtual
        override(Governor, GovernorGhostCounting)
        returns (bool)
    {
        uint256 totalVotes = token().getPastTotalSupply(proposalSnapshot(proposalId));
        (uint256 againstVotes, uint256 forVotes,) = proposalVotes(proposalId);
        uint256 rhs = 3 * (5 * againstVotes + forVotes) * Babylonian.sqrt(totalVotes);
        uint256 lhs = (forVotes + againstVotes);
        lhs = lhs * Babylonian.sqrt(80 * lhs + totalVotes);
        return lhs > rhs;
    }
}
