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

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

/**
 * @title PolicyMath
 * @notice Deterministic GRU M0/M1 rules: 1.2× reserves on M0; ≤5× M1 vs M0 per line.
 * @dev Must match docs/hybx-omnl/HYBX_OMNL_POLICY_SPEC.md
 */
library PolicyMath {
    uint256 internal constant M0_NUM = 12;
    uint256 internal constant M0_DEN = 10;
    uint256 internal constant M1_CAP_NUM = 5;
    uint256 internal constant M1_CAP_DEN = 1;

    /// @notice Minimum reserves R required to back S0 units of M0 (ceil(1.2 * S0)).
    function minReservesForM0(uint256 s0) internal pure returns (uint256) {
        return Math.mulDiv(s0, M0_NUM, M0_DEN, Math.Rounding.Ceil);
    }

    /// @notice Maximum M1 supply allowed for S0 units of M0 (5 * S0).
    function maxM1ForM0(uint256 s0) internal pure returns (uint256) {
        return Math.mulDiv(s0, M1_CAP_NUM, M1_CAP_DEN);
    }

    function m0BackingOk(uint256 s0, uint256 r) internal pure returns (bool) {
        return r >= minReservesForM0(s0);
    }

    function m1ExpansionOk(uint256 s0, uint256 s1) internal pure returns (bool) {
        return s1 <= maxM1ForM0(s0);
    }

    function isCompliant(uint256 s0, uint256 s1, uint256 r) internal pure returns (bool) {
        return m0BackingOk(s0, r) && m1ExpansionOk(s0, s1);
    }

    function canMintM0(uint256 s0Before, uint256 mintAmount, uint256 r) internal pure returns (bool) {
        unchecked {
            uint256 s0After = s0Before + mintAmount;
            return m0BackingOk(s0After, r);
        }
    }

    function canMintM1(uint256 s0, uint256 s1Before, uint256 mintAmount) internal pure returns (bool) {
        unchecked {
            uint256 s1After = s1Before + mintAmount;
            return m1ExpansionOk(s0, s1After);
        }
    }
}
