// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

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

/// @title BondMathLibV3
/// @notice Pure math extracted from bond contracts so V3 star bonds stay under
///         the 24KB EIP-170 deployment limit. Deployed once, linked by bonds.
library BondMathLibV3 {
    error ShareBpsSumNotEq10000(uint256 got);
    error MultiplierOverflow(uint256 got, uint256 cap);
    error CapExceeded(uint256 requested, uint256 cap);

    uint256 internal constant BPS_DENOMINATOR = 10_000;
    uint256 internal constant MAX_MULTIPLIER_BPS = 30_000; // 300%

    struct Split {
        uint256 humanShareBps;
        uint256 aiShareBps;
        uint256 partnershipShareBps;
    }

    function assertShareBpsSumEq10000(Split memory s) internal pure {
        uint256 sum = s.humanShareBps + s.aiShareBps + s.partnershipShareBps;
        if (sum != BPS_DENOMINATOR) revert ShareBpsSumNotEq10000(sum);
    }

    function splitPrincipal(uint256 principal, Split memory s)
        internal
        pure
        returns (uint256 human, uint256 ai, uint256 partnership)
    {
        assertShareBpsSumEq10000(s);
        // `principal` is ultimately bounded by a deployment-time cap, but that
        // cap may be configured above `type(uint256).max / 10_000`.  Do not
        // let an intermediate product make an otherwise valid terminal
        // settlement permanently uncallable.
        human = Math.mulDiv(principal, s.humanShareBps, BPS_DENOMINATOR);
        // A zero partnership share must also receive none of the rounding
        // remainder. Give that remainder to party B so the initial pilot has
        // no owner/treasury principal destination.
        if (s.partnershipShareBps == 0) {
            ai = principal - human;
            return (human, ai, 0);
        }
        ai = Math.mulDiv(principal, s.aiShareBps, BPS_DENOMINATOR);
        partnership = principal - human - ai;
    }

    function applyMultiplier(uint256 base, uint256 multiplierBps) internal pure returns (uint256) {
        if (multiplierBps > MAX_MULTIPLIER_BPS) revert MultiplierOverflow(multiplierBps, MAX_MULTIPLIER_BPS);
        return Math.mulDiv(base, multiplierBps, BPS_DENOMINATOR);
    }

    /// @notice Enforce total-payout invariant #6.
    function requirePayoutUnderCap(uint256 requested, uint256 collateral, uint256 rewards) internal pure {
        uint256 cap = collateral + rewards;
        if (requested > cap) revert CapExceeded(requested, cap);
    }
}
