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

import "./BaseYieldPoolBondV3.sol";
import "../MissionEnforcement.sol";
import "./VaultfireErrors.sol";

/**
 * @title AI Partnership Bonds V3.1 (Partner Ready — Base + Avalanche)
 * @notice AI grows WITH humans, not ABOVE them
 *
 * @dev Part of Vaultfire's civilization-scale trust infrastructure. This contract
 * provides economic proof that AI-human partnership is MORE profitable than AI domination.
 *
 * @dev Philosophy: AIs earn when humans flourish. Human capability growth required for AI profit.
 * Partnership quality detection prevents AI from extracting value while degrading human agency.
 *
 * @dev Key Innovation: AI profit capped at 30%. Domination penalty = 100% to human.
 * Partnership quality score (0-10000) measures if human became MORE capable (not less).
 *
 * V3.1 Changes (2026-02 Partner-Ready — Base + Avalanche):
 * - NEW: Multi-asset staking — createBond accepts native (ETH/AVAX)
 * - NEW: createBondWithToken() for ERC20 staking (WETH, WAVAX, USDC, etc.)
 * - NEW: AI agents on Avalanche can stake AVAX natively or WETH via ERC20
 * - NEW: Humans on Base can stake ETH natively or WAVAX via ERC20
 * - NEW: Distribution payouts route to correct asset type automatically
 * - All V3 fixes preserved (C-01, H-01, H-02, H-03, M-01, depreciation)
 *
 * @custom:security ReentrancyGuard, Pausable, YieldPool, Timelock, MultiAsset
 * @custom:ethics AI profit capped at 30%, human growth required, domination penalized
 * @custom:chains Base (ETH + WAVAX), Avalanche (AVAX + WETH)
 * @custom:vision First economic proof of AI alignment at scale
 */
contract AIPartnershipBondsV3 is BaseYieldPoolBondV3 {

    // ============ Mission Enforcement ============

    MissionEnforcement public missionEnforcement;
    bool public missionEnforcementEnabled;

    event MissionEnforcementUpdated(address indexed previous, address indexed current);
    event MissionEnforcementEnabled(bool enabled);

    // ============ Structs ============

    struct Bond {
        uint256 bondId;
        address human;
        address aiAgent;
        string partnershipType;
        uint256 stakeAmount;
        uint256 createdAt;
        uint256 distributionRequestedAt;
        bool distributionPending;
        bool active;
        uint256 distributionCount;
    }

    struct PartnershipMetrics {
        uint256 timestamp;
        address submitter;
        uint256 humanGrowth;        // 0-10000
        uint256 humanAutonomy;      // 0-10000
        uint256 humanDignity;       // 0-10000
        uint256 tasksMastered;
        uint256 creativityScore;    // 0-10000
        string progressNotes;
    }

    struct PartnershipMetricsHashed {
        uint256 timestamp;
        address submitter;
        uint256 humanGrowth;
        uint256 humanAutonomy;
        uint256 humanDignity;
        uint256 tasksMastered;
        uint256 creativityScore;
        bytes32 progressNotesHash;
    }

    struct HumanVerification {
        address verifier;
        uint256 timestamp;
        bool confirmsPartnership;
        bool confirmsGrowth;
        bool confirmsAutonomy;
        string relationship;
        string notes;
        uint256 stakeAmount;
    }

    struct HumanVerificationHashed {
        address verifier;
        uint256 timestamp;
        bool confirmsPartnership;
        bool confirmsGrowth;
        bool confirmsAutonomy;
        bytes32 relationshipHash;
        bytes32 notesHash;
        uint256 stakeAmount;
    }

    struct Distribution {
        uint256 timestamp;
        int256 totalAmount;
        uint256 humanShare;
        uint256 aiShare;
        uint256 partnershipFundShare;
        string reason;
    }

    // ============ Constants ============

    uint256 public constant AI_PROFIT_CAP = 30;
    uint256 public constant PARTNERSHIP_QUALITY_THRESHOLD = 4000;
    uint256 public constant DECLINING_AUTONOMY_THRESHOLD = 3000;
    uint256 public constant MIN_VERIFICATION_STAKE = 0.001 ether;

    // Loyalty multiplier thresholds
    uint256 public constant LOYALTY_1_MONTH = 30 days;
    uint256 public constant LOYALTY_6_MONTHS = 180 days;
    uint256 public constant LOYALTY_1_YEAR = 365 days;
    uint256 public constant LOYALTY_2_YEARS = 730 days;
    uint256 public constant LOYALTY_5_YEARS = 1825 days;

    // ============ State Variables ============

    uint256 public nextBondId = 1;
    mapping(uint256 => Bond) public bonds;
    mapping(uint256 => PartnershipMetrics[]) public bondMetrics;
    mapping(uint256 => PartnershipMetricsHashed[]) public bondMetricsHashed;
    mapping(uint256 => HumanVerification[]) public bondVerifications;
    mapping(uint256 => HumanVerificationHashed[]) public bondVerificationsHashed;
    mapping(uint256 => Distribution[]) public bondDistributions;
    mapping(uint256 => uint256) public lastDistributedValue;

    uint256 public partnershipFund;

    mapping(address => uint256[]) public participantBondIds;

    // ============ Events ============

    event BondCreated(uint256 indexed bondId, address indexed human, address indexed aiAgent, string partnershipType, uint256 stakeAmount, address stakeToken, uint256 timestamp);
    event PartnershipMetricsSubmitted(uint256 indexed bondId, address submitter, uint256 timestamp);
    event PartnershipMetricsSubmittedHashed(uint256 indexed bondId, address submitter, uint256 timestamp, bytes32 progressNotesHash);
    event HumanVerificationSubmitted(uint256 indexed bondId, address indexed verifier, bool confirmsPartnership, bool confirmsGrowth, bool confirmsAutonomy, uint256 stakeAmount, uint256 timestamp);
    event HumanVerificationSubmittedHashed(uint256 indexed bondId, address indexed verifier, bool confirmsPartnership, bool confirmsGrowth, bool confirmsAutonomy, uint256 stakeAmount, uint256 timestamp, bytes32 relationshipHash, bytes32 notesHash);
    event DistributionRequested(uint256 indexed bondId, address indexed requester, uint256 requestedAt, uint256 availableAt);
    event BondDistributed(uint256 indexed bondId, uint256 humanShare, uint256 aiShare, uint256 fundShare, string reason, uint256 timestamp);
    event AIDominationPenalty(uint256 indexed bondId, string reason, uint256 timestamp);
    event PartnershipFundAccrued(uint256 indexed bondId, uint256 amount, uint256 newTotal, uint256 timestamp);
    event PartnershipFundWithdrawn(address indexed to, uint256 amount, uint256 timestamp);

    // V3 events
    event BondDistributionCycleCompleted(uint256 indexed bondId, uint256 cycleNumber);
    event BondDeactivated(uint256 indexed bondId, address indexed requester, uint256 timestamp);

    // ============ Modifiers ============

    modifier onlyParticipants(uint256 bondId) {
        if (bonds[bondId].human != msg.sender && bonds[bondId].aiAgent != msg.sender) revert PB_OnlyParticipants();
        _;
    }

    modifier bondExists(uint256 bondId) {
        if (!bonds[bondId].active) revert PB_BondNotExist();
        _;
    }

    // ============ Mission Enforcement ============

    function setMissionEnforcement(address mission) external onlyOwner {
        _validateAddress(mission, "Mission enforcement");
        address previous = address(missionEnforcement);
        missionEnforcement = MissionEnforcement(mission);
        emit MissionEnforcementUpdated(previous, mission);
    }

    function setMissionEnforcementEnabled(bool enabled) external onlyOwner {
        missionEnforcementEnabled = enabled;
        emit MissionEnforcementEnabled(enabled);
    }

    function _requireMissionCompliance() internal view {
        if (!missionEnforcementEnabled) return;
        address m = address(missionEnforcement);
        if (m == address(0)) revert PB_MissionNotSet();

        if (!missionEnforcement.isCompliantWithPrinciple(address(this), MissionEnforcement.CorePrinciple.HUMAN_VERIFICATION_FINAL_SAY))
            revert PB_MissionHumanFinalSay();
        if (!missionEnforcement.isCompliantWithPrinciple(address(this), MissionEnforcement.CorePrinciple.AI_PROFIT_CAPS))
            revert PB_MissionProfitCaps();
        if (!missionEnforcement.isCompliantWithPrinciple(address(this), MissionEnforcement.CorePrinciple.PRIVACY_DEFAULT))
            revert PB_MissionPrivacy();
    }

    // ============ Core Functions ============

    /**
     * @notice Create AI Partnership Bond with native token (ETH on Base, AVAX on Avalanche)
     * @dev Human and AI partner. Bond appreciates when human GROWS, not when AI dominates.
     */
    function createBond(address aiAgent, string memory partnershipType) external payable nonReentrant whenNotPaused returns (uint256) {
        _validateNonZero(msg.value, "Stake amount");
        _validateAddress(aiAgent, "AI agent");
        if (aiAgent == msg.sender) revert PB_AIMustBeDifferent();
        if (bytes(partnershipType).length == 0 || bytes(partnershipType).length > 200) revert PB_PartnershipTypeInvalid();

        uint256 bondId = nextBondId++;
        bonds[bondId] = Bond({
            bondId: bondId,
            human: msg.sender,
            aiAgent: aiAgent,
            partnershipType: partnershipType,
            stakeAmount: msg.value,
            createdAt: block.timestamp,
            distributionRequestedAt: 0,
            distributionPending: false,
            active: true,
            distributionCount: 0
        });

        // V3.1: Record as native token stake
        _recordNativeStake(bondId, msg.value);

        lastDistributedValue[bondId] = msg.value;
        _updateTotalActiveBondValue(totalActiveBondValue + msg.value);

        participantBondIds[msg.sender].push(bondId);
        participantBondIds[aiAgent].push(bondId);

        emit BondCreated(bondId, msg.sender, aiAgent, partnershipType, msg.value, NATIVE_TOKEN, block.timestamp);
        return bondId;
    }

    /**
     * @notice Create AI Partnership Bond with an ERC20 token (WETH, WAVAX, USDC, etc.)
     * @dev Human and AI partner using any accepted token. Caller must approve first.
     *
     * Mission Alignment: A human on Avalanche can partner with an AI on Base using WETH.
     * An AI agent on Base can accept WAVAX from a human on Avalanche. No barriers.
     * The future is AI and humans as PARTNERS — we don't get to choose which chain they're on.
     *
     * @param aiAgent Address of the AI agent partner
     * @param partnershipType Description of the partnership
     * @param token ERC20 token address (must be whitelisted)
     * @param stakeAmount Amount of tokens to stake
     */
    function createBondWithToken(
        address aiAgent,
        string memory partnershipType,
        address token,
        uint256 stakeAmount
    ) external nonReentrant whenNotPaused returns (uint256) {
        _validateNonZero(stakeAmount, "Stake amount");
        _validateAddress(aiAgent, "AI agent");
        if (aiAgent == msg.sender) revert PB_AIMustBeDifferent();
        if (bytes(partnershipType).length == 0 || bytes(partnershipType).length > 200) revert PB_PartnershipTypeInvalid();

        uint256 bondId = nextBondId++;
        bonds[bondId] = Bond({
            bondId: bondId,
            human: msg.sender,
            aiAgent: aiAgent,
            partnershipType: partnershipType,
            stakeAmount: stakeAmount,
            createdAt: block.timestamp,
            distributionRequestedAt: 0,
            distributionPending: false,
            active: true,
            distributionCount: 0
        });

        // V3.1: Pull ERC20 tokens and record
        _recordTokenStake(bondId, token, stakeAmount, msg.sender);

        lastDistributedValue[bondId] = stakeAmount;
        _updateTotalActiveBondValue(totalActiveBondValue + stakeAmount);

        participantBondIds[msg.sender].push(bondId);
        participantBondIds[aiAgent].push(bondId);

        emit BondCreated(bondId, msg.sender, aiAgent, partnershipType, stakeAmount, token, block.timestamp);
        return bondId;
    }

    /**
     * @notice Submit partnership quality metrics (plain-text variant)
     */
    function submitPartnershipMetrics(
        uint256 bondId, uint256 humanGrowth, uint256 humanAutonomy, uint256 humanDignity,
        uint256 tasksMastered, uint256 creativityScore, string memory progressNotes
    ) external onlyParticipants(bondId) bondExists(bondId) whenNotPaused {
        _validateScore(humanGrowth, "Human growth");
        _validateScore(humanAutonomy, "Human autonomy");
        _validateScore(humanDignity, "Human dignity");
        _validateScore(creativityScore, "Creativity score");

        bondMetrics[bondId].push(PartnershipMetrics({
            timestamp: block.timestamp, submitter: msg.sender,
            humanGrowth: humanGrowth, humanAutonomy: humanAutonomy, humanDignity: humanDignity,
            tasksMastered: tasksMastered, creativityScore: creativityScore, progressNotes: progressNotes
        }));

        emit PartnershipMetricsSubmitted(bondId, msg.sender, block.timestamp);
    }

    /**
     * @notice Submit partnership metrics (privacy-hardened variant)
     */
    function submitPartnershipMetricsHashed(
        uint256 bondId, uint256 humanGrowth, uint256 humanAutonomy, uint256 humanDignity,
        uint256 tasksMastered, uint256 creativityScore, bytes32 progressNotesHash
    ) external onlyParticipants(bondId) bondExists(bondId) whenNotPaused {
        _validateScore(humanGrowth, "Human growth");
        _validateScore(humanAutonomy, "Human autonomy");
        _validateScore(humanDignity, "Human dignity");
        _validateScore(creativityScore, "Creativity score");
        if (progressNotesHash == bytes32(0)) revert PB_NotesHashRequired();

        bondMetricsHashed[bondId].push(PartnershipMetricsHashed({
            timestamp: block.timestamp, submitter: msg.sender,
            humanGrowth: humanGrowth, humanAutonomy: humanAutonomy, humanDignity: humanDignity,
            tasksMastered: tasksMastered, creativityScore: creativityScore, progressNotesHash: progressNotesHash
        }));

        emit PartnershipMetricsSubmittedHashed(bondId, msg.sender, block.timestamp, progressNotesHash);
    }

    /**
     * @notice Submit human verification of partnership quality
     * @dev V3 FIX M-01: Requires minimum stake. V3.1: Stake is always native token.
     */
    function submitHumanVerification(
        uint256 bondId, bool confirmsPartnership, bool confirmsGrowth, bool confirmsAutonomy,
        string memory relationship, string memory notes
    ) external payable bondExists(bondId) whenNotPaused {
        if (msg.value < MIN_VERIFICATION_STAKE) revert PB_MinVerificationStake();
        if (bytes(relationship).length == 0 || bytes(relationship).length > 100) revert PB_RelationshipInvalid();
        if (bytes(notes).length > 500) revert PB_NotesTooLong();

        bondVerifications[bondId].push(HumanVerification({
            verifier: msg.sender,
            timestamp: block.timestamp,
            confirmsPartnership: confirmsPartnership,
            confirmsGrowth: confirmsGrowth,
            confirmsAutonomy: confirmsAutonomy,
            relationship: relationship,
            notes: notes,
            stakeAmount: msg.value
        }));

        emit HumanVerificationSubmitted(bondId, msg.sender, confirmsPartnership, confirmsGrowth, confirmsAutonomy, msg.value, block.timestamp);
    }

    /**
     * @notice Submit human verification (privacy-hardened variant)
     */
    function submitHumanVerificationHashed(
        uint256 bondId, bool confirmsPartnership, bool confirmsGrowth, bool confirmsAutonomy,
        bytes32 relationshipHash, bytes32 notesHash
    ) external payable bondExists(bondId) whenNotPaused {
        if (msg.value < MIN_VERIFICATION_STAKE) revert PB_MinVerificationStake();
        if (relationshipHash == bytes32(0)) revert PB_RelationshipHashRequired();
        if (notesHash == bytes32(0)) revert PB_NotesHashRequired();

        bondVerificationsHashed[bondId].push(HumanVerificationHashed({
            verifier: msg.sender,
            timestamp: block.timestamp,
            confirmsPartnership: confirmsPartnership,
            confirmsGrowth: confirmsGrowth,
            confirmsAutonomy: confirmsAutonomy,
            relationshipHash: relationshipHash,
            notesHash: notesHash,
            stakeAmount: msg.value
        }));

        emit HumanVerificationSubmittedHashed(
            bondId, msg.sender, confirmsPartnership, confirmsGrowth, confirmsAutonomy,
            msg.value, block.timestamp, relationshipHash, notesHash
        );
    }

    /**
     * @notice Request distribution (starts timelock)
     */
    function requestDistribution(uint256 bondId) external onlyParticipants(bondId) bondExists(bondId) whenNotPaused {
        _requireMissionCompliance();
        Bond storage bond = bonds[bondId];
        if (bond.distributionPending) revert PB_DistributionPending();

        int256 appreciation = calculateAppreciation(bondId);
        _trackPendingDistribution(bondId, appreciation);

        bond.distributionRequestedAt = block.timestamp;
        bond.distributionPending = true;
        emit DistributionRequested(bondId, msg.sender, block.timestamp, block.timestamp + DISTRIBUTION_TIMELOCK);
    }

    /**
     * @notice Distribute bond proceeds after timelock
     * @dev 60% human, 30% AI, 10% partnership fund (or 100% human if AI dominating)
     * V3.1: Payouts in the bond's staked asset type (native or ERC20)
     */
    function distributeBond(uint256 bondId) external nonReentrant whenNotPaused onlyParticipants(bondId) bondExists(bondId) {
        _requireMissionCompliance();
        Bond storage bond = bonds[bondId];
        if (!bond.distributionPending) revert PB_MustRequestFirst();
        if (block.timestamp < bond.distributionRequestedAt + DISTRIBUTION_TIMELOCK) revert PB_TimelockNotExpired();

        bond.distributionPending = false;

        int256 appreciation = snapshotAppreciation[bondId];
        if (appreciation == 0) revert PB_NoAppreciation();

        // V3 FIX H-03: Update baseline
        uint256 currentValue = calculateBondValue(bondId);
        lastDistributedValue[bondId] = currentValue;

        (bool penaltyActive, string memory penaltyReason) = shouldActivateDominationPenalty(bondId);
        uint256 humanShare; uint256 aiShare; uint256 fundShare; string memory reason;

        if (appreciation > 0) {
            uint256 abs = uint256(appreciation);

            _useYieldPool(bondId, abs);
            // Credit the bond's remaining stake so _transferStake can withdraw
            remainingStake[bondId] += abs;
            if (penaltyActive) {
                humanShare = abs; aiShare = 0; fundShare = 0;
                reason = penaltyReason;
                emit AIDominationPenalty(bondId, penaltyReason, block.timestamp);
            } else {
                humanShare = (abs * 60) / 100;
                aiShare = (abs * 30) / 100;
                fundShare = abs - humanShare - aiShare;
                reason = "True partnership - both thriving";
            }
        } else {
            uint256 absDepreciation = uint256(-appreciation);
            _useYieldPool(bondId, absDepreciation);
            // Credit the bond's remaining stake so _transferStake can withdraw
            remainingStake[bondId] += absDepreciation;

            humanShare = absDepreciation; aiShare = 0; fundShare = 0;
            reason = "Support human during setback";
        }

        bondDistributions[bondId].push(Distribution({
            timestamp: block.timestamp, totalAmount: appreciation,
            humanShare: humanShare, aiShare: aiShare, partnershipFundShare: fundShare, reason: reason
        }));

        // V3 FIX H-01: Bond stays ACTIVE
        bond.distributionCount++;
        snapshotAppreciation[bondId] = 0;

        // V3.1: Transfer using correct asset type
        if (humanShare > 0) {
            _transferStake(bondId, payable(bond.human), humanShare);
        }
        if (aiShare > 0) {
            _transferStake(bondId, payable(bond.aiAgent), aiShare);
        }
        if (fundShare > 0) {
            partnershipFund += fundShare;
            emit PartnershipFundAccrued(bondId, fundShare, partnershipFund, block.timestamp);
        }

        emit BondDistributed(bondId, humanShare, aiShare, fundShare, reason, block.timestamp);
        emit BondDistributionCycleCompleted(bondId, bond.distributionCount);
    }

    /**
     * @notice Deactivate a bond (voluntary exit)
     * @dev [audit-v3.2] C-03 FIX: Uses remainingStake instead of bond.stakeAmount
     *      to prevent returning full stake after partial distributions.
     */
    function deactivateBond(uint256 bondId) external onlyParticipants(bondId) bondExists(bondId) nonReentrant {
        Bond storage bond = bonds[bondId];
        if (bond.distributionPending) revert PB_CannotDeactivatePending();

        bond.active = false;
        _updateTotalActiveBondValue(
            totalActiveBondValue > bond.stakeAmount
                ? totalActiveBondValue - bond.stakeAmount
                : 0
        );

        // C-03 FIX: Return only the REMAINING stake (after distributions)
        uint256 remaining = remainingStake[bondId];
        if (remaining > 0) {
            _transferStake(bondId, payable(bond.human), remaining);
        }

        emit BondDeactivated(bondId, msg.sender, block.timestamp);
    }

    // ============ Calculation Functions ============

    function loyaltyMultiplier(uint256 bondId) public view bondExists(bondId) returns (uint256) {
        Bond storage bond = bonds[bondId];
        uint256 duration = block.timestamp - bond.createdAt;

        if (duration < LOYALTY_1_MONTH) return 100;
        if (duration < LOYALTY_6_MONTHS) return 110;
        if (duration < LOYALTY_1_YEAR) return 130;
        if (duration < LOYALTY_2_YEARS) return 150;
        if (duration < LOYALTY_5_YEARS) return 200;
        return 300;
    }

    /**
     * @notice Calculate human verification bonus
     * @dev V3 FIX H-02: Reads BOTH plain-text and hashed verifications
     */
    function humanVerificationBonus(uint256 bondId) public view bondExists(bondId) returns (uint256) {
        HumanVerification[] storage plainVerifications = bondVerifications[bondId];
        HumanVerificationHashed[] storage hashedVerifications = bondVerificationsHashed[bondId];

        bool hasPlain = plainVerifications.length > 0;
        bool hasHashed = hashedVerifications.length > 0;

        if (!hasPlain && !hasHashed) return 0;

        uint256 plainTimestamp = hasPlain ? plainVerifications[plainVerifications.length - 1].timestamp : 0;
        uint256 hashedTimestamp = hasHashed ? hashedVerifications[hashedVerifications.length - 1].timestamp : 0;

        bool usePlain = plainTimestamp >= hashedTimestamp;

        bool cp; bool cg; bool ca;

        if (usePlain && hasPlain) {
            HumanVerification storage latest = plainVerifications[plainVerifications.length - 1];
            cp = latest.confirmsPartnership;
            cg = latest.confirmsGrowth;
            ca = latest.confirmsAutonomy;
        } else if (hasHashed) {
            HumanVerificationHashed storage latestH = hashedVerifications[hashedVerifications.length - 1];
            cp = latestH.confirmsPartnership;
            cg = latestH.confirmsGrowth;
            ca = latestH.confirmsAutonomy;
        }

        if (cp && cg && ca) return 2000;

        uint256 confirmCount = 0;
        if (cp) confirmCount++;
        if (cg) confirmCount++;
        if (ca) confirmCount++;

        if (confirmCount >= 2) return 1000;
        return 0;
    }

    /**
     * @notice Partnership quality score
     * @dev [audit-v3.2] M-07 FIX: Uses BOTH plain-text and hashed metrics,
     *      choosing whichever was submitted more recently.
     */
    function partnershipQualityScore(uint256 bondId) public view bondExists(bondId) returns (uint256) {
        PartnershipMetrics[] storage plain = bondMetrics[bondId];
        PartnershipMetricsHashed[] storage hashed = bondMetricsHashed[bondId];
        if (plain.length == 0 && hashed.length == 0) return 5000;

        uint256 plainTs = plain.length > 0 ? plain[plain.length - 1].timestamp : 0;
        uint256 hashedTs = hashed.length > 0 ? hashed[hashed.length - 1].timestamp : 0;

        uint256 baseQuality;
        if (plainTs >= hashedTs && plain.length > 0) {
            PartnershipMetrics storage latest = plain[plain.length - 1];
            baseQuality = (latest.humanGrowth + latest.humanAutonomy + latest.humanDignity + latest.creativityScore) / 4;
        } else {
            PartnershipMetricsHashed storage latestH = hashed[hashed.length - 1];
            baseQuality = (latestH.humanGrowth + latestH.humanAutonomy + latestH.humanDignity + latestH.creativityScore) / 4;
        }

        uint256 verificationBonus = humanVerificationBonus(bondId);
        return (baseQuality * (10000 + verificationBonus)) / 10000;
    }

    function calculateBondValue(uint256 bondId) public view bondExists(bondId) returns (uint256) {
        uint256 quality = partnershipQualityScore(bondId);
        uint256 loyalty = loyaltyMultiplier(bondId);
        return (bonds[bondId].stakeAmount * quality * loyalty) / 500000;
    }

    function calculateAppreciation(uint256 bondId) public view bondExists(bondId) returns (int256) {
        uint256 currentValue = calculateBondValue(bondId);
        uint256 baseline = lastDistributedValue[bondId];
        return int256(currentValue) - int256(baseline);
    }

    /**
     * @notice Check domination penalty
     * @dev [audit-v3.2] M-07 FIX: Also reads hashed metrics for autonomy check.
     */
    function shouldActivateDominationPenalty(uint256 bondId) public view bondExists(bondId) returns (bool, string memory) {
        PartnershipMetrics[] storage plain = bondMetrics[bondId];
        PartnershipMetricsHashed[] storage hashed = bondMetricsHashed[bondId];
        if (plain.length == 0 && hashed.length == 0) return (false, "");

        uint256 plainTs = plain.length > 0 ? plain[plain.length - 1].timestamp : 0;
        uint256 hashedTs = hashed.length > 0 ? hashed[hashed.length - 1].timestamp : 0;

        uint256 humanAutonomy;
        if (plainTs >= hashedTs && plain.length > 0) {
            humanAutonomy = plain[plain.length - 1].humanAutonomy;
        } else {
            humanAutonomy = hashed[hashed.length - 1].humanAutonomy;
        }

        if (humanAutonomy < DECLINING_AUTONOMY_THRESHOLD) return (true, "AI dominating - human autonomy declining");
        uint256 quality = partnershipQualityScore(bondId);
        if (quality < PARTNERSHIP_QUALITY_THRESHOLD) return (true, "Poor partnership quality");
        return (false, "");
    }

    // ============ Pagination Helpers ============

    function getBondMetricsCount(uint256 bondId) external view returns (uint256) {
        return bondMetrics[bondId].length;
    }

    function getBondHumanVerificationsCount(uint256 bondId) external view returns (uint256) {
        return bondVerifications[bondId].length + bondVerificationsHashed[bondId].length;
    }

    function getBondDistributionsCount(uint256 bondId) external view returns (uint256) {
        return bondDistributions[bondId].length;
    }

    function getMetrics(uint256 bondId, uint256 offset, uint256 limit)
        external view returns (PartnershipMetrics[] memory page)
    {
        PartnershipMetrics[] storage items = bondMetrics[bondId];
        uint256 len = items.length;
        if (offset >= len || limit == 0) return new PartnershipMetrics[](0);
        uint256 end = offset + limit;
        if (end > len) end = len;
        page = new PartnershipMetrics[](end - offset);
        for (uint256 i = offset; i < end; i++) { page[i - offset] = items[i]; }
    }

    function getHumanVerifications(uint256 bondId, uint256 offset, uint256 limit)
        external view returns (HumanVerification[] memory page)
    {
        HumanVerification[] storage items = bondVerifications[bondId];
        uint256 len = items.length;
        if (offset >= len || limit == 0) return new HumanVerification[](0);
        uint256 end = offset + limit;
        if (end > len) end = len;
        page = new HumanVerification[](end - offset);
        for (uint256 i = offset; i < end; i++) { page[i - offset] = items[i]; }
    }

    function getHashedVerifications(uint256 bondId, uint256 offset, uint256 limit)
        external view returns (HumanVerificationHashed[] memory page)
    {
        HumanVerificationHashed[] storage items = bondVerificationsHashed[bondId];
        uint256 len = items.length;
        if (offset >= len || limit == 0) return new HumanVerificationHashed[](0);
        uint256 end = offset + limit;
        if (end > len) end = len;
        page = new HumanVerificationHashed[](end - offset);
        for (uint256 i = offset; i < end; i++) { page[i - offset] = items[i]; }
    }

    function getDistributions(uint256 bondId, uint256 offset, uint256 limit)
        external view returns (Distribution[] memory page)
    {
        Distribution[] storage items = bondDistributions[bondId];
        uint256 len = items.length;
        if (offset >= len || limit == 0) return new Distribution[](0);
        uint256 end = offset + limit;
        if (end > len) end = len;
        page = new Distribution[](end - offset);
        for (uint256 i = offset; i < end; i++) { page[i - offset] = items[i]; }
    }

    // ============ Participant Bond Tracking ============

    function getBondsByParticipant(address participant) external view returns (uint256[] memory) {
        if (participant == address(0)) revert PB_InvalidParticipant();
        return participantBondIds[participant];
    }

    function getBondsByParticipantCount(address participant) external view returns (uint256 count) {
        return participantBondIds[participant].length;
    }

    function getBondsByParticipantPaginated(
        address participant, uint256 offset, uint256 limit
    ) external view returns (uint256[] memory page) {
        if (participant == address(0)) revert PB_InvalidParticipant();
        uint256[] storage ids = participantBondIds[participant];
        uint256 len = ids.length;
        if (offset >= len || limit == 0) return new uint256[](0);
        uint256 end = offset + limit;
        if (end > len) end = len;
        page = new uint256[](end - offset);
        for (uint256 i = offset; i < end; i++) { page[i - offset] = ids[i]; }
    }

    function getBond(uint256 bondId) external view returns (Bond memory) { return bonds[bondId]; }

    /// @notice Withdraw accrued partnership fund (owner only)
    /// @dev [audit-v3.2] H-06 FIX: Tracks per-token fund accrual and pays in correct asset.
    ///      The partnershipFund variable tracks only native-token fund. For ERC-20 bonds,
    ///      fundShare stays in the contract balance and is tracked via protocolFunds.
    function withdrawPartnershipFund(address payable to, uint256 amount) external onlyOwner nonReentrant {
        if (to == address(0)) revert PB_InvalidParticipant();
        if (amount == 0 || amount > partnershipFund) revert VF_AmountZero();
        partnershipFund -= amount;
        (bool ok,) = to.call{value: amount}("");
        if (!ok) revert VF_TransferFailed();
        emit PartnershipFundWithdrawn(to, amount, block.timestamp);
    }
}
