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

import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
import {IUniswapV2Pair} from "@uniswap-v2-core/interfaces/IUniswapV2Pair.sol";

import {GhostAccessControlled} from "./types/GhostAccessControlled.sol";
import {FullMath} from "./libraries/FullMath.sol";

import {ITreasury} from "./interfaces/ITreasury.sol";
import {IDistributor} from "./interfaces/IDistributor.sol";
import {IGhostAuthority} from "./interfaces/IGhostAuthority.sol";

contract GhostDistributor is IDistributor, GhostAccessControlled {
    uint256 private constant DENOMINATOR = 1e6;

    address private immutable ftso;     // forge-lint: disable-line(screaming-snake-case-immutable)
    address private immutable treasury; // forge-lint: disable-line(screaming-snake-case-immutable)
    address private immutable staking;  // forge-lint: disable-line(screaming-snake-case-immutable)

    uint256 public rewardRate;
    uint256 public bounty;

    Adjust public adjustment;
    address[] public pools;

    constructor(
        address _treasury,
        address _ftso,
        address _staking,
        address _authority,
        uint256 _rewardRate
    ) GhostAccessControlled(IGhostAuthority(_authority)) {
        treasury = _treasury;
        ftso = _ftso;
        staking = _staking;
        rewardRate = _rewardRate;
    }

    function distribute() external override {
        if (msg.sender != staking) revert OnlyStaking();

        ITreasury(treasury).mint(staking, nextRewardFor(staking));

        uint256 i;
        for (; i < pools.length; ) {
            address pool = pools[i];
            if (pool != address(0)) {
                ITreasury(treasury).mint(pool, nextRewardFor(pool));
                IUniswapV2Pair(pool).sync();
            }
            unchecked { ++i; }
        }

        if (adjustment.rate > 0) _adjust();
    }

    function retrieveBounty() external override returns (uint256) {
        if (msg.sender != staking) revert OnlyStaking();
        if (bounty > 0) ITreasury(treasury).mint(staking, bounty);
        return bounty;
    }

    function _adjust() internal {
        if (adjustment.add) {
            rewardRate = rewardRate + adjustment.rate;
            if (rewardRate >= adjustment.target) {
                adjustment.rate = 0;
                rewardRate = adjustment.target;
            }
        } else {
            rewardRate = rewardRate > adjustment.rate ? rewardRate - adjustment.rate : 0;
            if (rewardRate <= adjustment.target) {
                adjustment.rate = 0;
                rewardRate = adjustment.target;
            }
        }
    }

    function nextRewardFor(address who) public view override returns (uint256) {
        return FullMath.mulDiv(IERC20(ftso).balanceOf(who), rewardRate, DENOMINATOR);
    }

    function setBounty(uint256 _bounty) external override onlyGovernor {
        bounty = _bounty;
    }

    function setPools(address[] calldata _pools) external override onlyGovernor {
        pools = _pools;
    }

    function removePool(uint256 index) external override onlyGovernor {
        uint256 length = pools.length;
        pools[index] = pools[length - 1];
        pools.pop();
    }

    function addPool(address pool) external override onlyGovernor {
        pools.push(pool);
    }

    function setAdjustment(
        uint256 rate,
        uint256 target,
        bool add
    ) external {
        if (
            msg.sender != authority.governor() &&
            msg.sender != authority.guardian()
        ) revert NotPermissioned();
        if (
            msg.sender == authority.guardian() &&
            rate > FullMath.mulDiv(rewardRate, 25, 1_000)
        ) revert AdjustmentLimit();
        if (!add && rate > rewardRate) revert AdjustmentUnderflow();

        adjustment = Adjust({
            add: add,
            rate: uint120(rate),        // forge-lint: disable-line(unsafe-typecast)
            target: uint120(target)     // forge-lint: disable-line(unsafe-typecast)
        });
    }
}
