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

import {IGhostWarmup} from "./interfaces/IGhostWarmup.sol";
import {IGHST} from "./interfaces/IGHST.sol";

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

contract GhostWarmup is IGhostWarmup {
    address public immutable STAKING;
    address public immutable GHST;

    uint256 private _ghstInWarmup;
    mapping(address => Claim) private _warmupInfo;

    constructor(address ghst) {
        STAKING = msg.sender;
        GHST = ghst;
    }

    function addToWarmup(
        uint256 payout,
        address who,
        uint48 expiry
    ) external override {
        if (msg.sender != STAKING) revert NotStakingContract();

        Claim storage info = _warmupInfo[who];
        uint256 ghstPayout = IGHST(GHST).balanceTo(payout);

        info.deposit += payout;
        info.payout += ghstPayout;
        info.expiry = expiry;

        _ghstInWarmup += ghstPayout;
    }

    function claim(
        address to,
        uint256 epochNumber
    ) external override returns (uint256 claimedAmount) {
        if (msg.sender != STAKING) revert NotStakingContract();
        Claim memory info = _warmupInfo[to];

        if (epochNumber >= info.expiry && info.expiry > 0) {
            delete _warmupInfo[to];
            _ghstInWarmup -= info.payout;
            claimedAmount = info.payout;
        }
    }

    function claimByAmount(
        address who,
        uint256 payout,
        uint256 epochNumber
    ) external override returns (uint256) {
        if (msg.sender != STAKING) revert NotStakingContract();
        return _reduceWarmupInfo(who, payout, epochNumber);
    }

    function breakout(
        address who,
        uint256 payout
    ) external override returns (uint256) {
        if (msg.sender != STAKING) revert NotStakingContract();
        return _reduceWarmupInfo(who, payout, type(uint256).max);
    }

    function forfeit(address who) external override returns (uint256) {
        if (msg.sender != STAKING) revert NotStakingContract();

        Claim memory info = _warmupInfo[who];
        delete _warmupInfo[who];
        _ghstInWarmup -= info.payout;
        return info.deposit;
    }

    function ghstInWarmup() external view override returns (uint256) {
        return _ghstInWarmup;
    }

    function warmupInfo(address who) external view override returns (uint256, uint256, uint48) {
        Claim memory info = _warmupInfo[who];
        return (info.deposit, info.payout, info.expiry);
    }

    function _reduceWarmupInfo(
        address who,
        uint256 amount,
        uint256 epochNumber
    ) private returns (uint256) {
        Claim storage info = _warmupInfo[who];

        if (epochNumber >= info.expiry && info.expiry > 0) {
            uint256 mm = mulmod(info.deposit, amount, info.payout);
            uint256 depositReduction = FullMath.mulDiv(info.deposit, amount, info.payout);
            if (mm > 0) depositReduction += 1;

            info.deposit -= depositReduction;
            info.payout -= amount;
            _ghstInWarmup -= amount;

            if (info.payout == 0) delete _warmupInfo[who];

            return amount;
        }

        revert LockedInWarmupPeriod();
    }
}
