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

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

/**
 * @title AI Accountability Bonds V3.1 (Partner Ready — Base + Avalanche)
 * @notice AI can only profit when ALL humans thrive - Works even with ZERO employment
 *
 * @dev Philosophy: The ONLY economic system that works when AI fires everyone.
 * Creates AI companies that profit from human thriving, not human obsolescence.
 *
 * @dev Key Innovation: Works with ZERO employment.
 * Measures purpose/education, not jobs.
 *
 * @dev Mission Alignment: Protects ALL HUMANS (not just workers).
 * Locks AI profits when humans suffering, regardless of employment status.
 *
 * V3.1 Changes (2026-02 Partner-Ready — Base + Avalanche):
 * - NEW: Multi-asset staking — createBond accepts native (ETH/AVAX) or whitelisted ERC20
 * - NEW: createBondWithToken() for ERC20 staking (WETH on Avalanche, WAVAX on Base, etc.)
 * - NEW: Distribution payouts route to correct asset type automatically
 * - All V3 fixes preserved (C-02, C-03, H-01, H-03, M-02, depreciation, oracle threshold)
 *
 * @custom:security ReentrancyGuard, Pausable, YieldPool, Timelock, MultiAsset
 * @custom:ethics 100% to humans when suffering, works with zero jobs
 * @custom:chains Base (ETH + WAVAX), Avalanche (AVAX + WETH)
 */
contract AIAccountabilityBondsV3 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 aiCompany;
        string companyName;
        uint256 quarterlyRevenue;
        uint256 stakeAmount;
        uint256 createdAt;
        uint256 distributionRequestedAt;
        bool distributionPending;
        bool active;
        uint256 distributionCount;
    }

    struct GlobalFlourishingMetrics {
        uint256 timestamp;
        uint256 incomeDistributionScore;  // 0-10000
        uint256 povertyRateScore;         // 0-10000
        uint256 healthOutcomesScore;      // 0-10000
        uint256 mentalHealthScore;        // 0-10000
        uint256 educationAccessScore;     // 0-10000
        uint256 purposeAgencyScore;       // 0-10000
    }

    struct Distribution {
        uint256 timestamp;
        int256 totalAmount;
        uint256 humanShare;
        uint256 aiCompanyShare;
        uint256 globalFlourishingScore;
        string reason;
    }

    struct OracleSource {
        address oracleAddress;
        string sourceName;
        bool isActive;
        uint256 registeredAt;
        uint256 trustScore;  // 0-10000
    }

    struct AIVerification {
        uint256 bondId;
        address verifyingAI;
        string verifyingCompanyName;
        uint256 timestamp;
        bool confirmsMetrics;
        string notes;
        uint256 stakeAmount;
        bool stakeReturned;
    }

    struct MetricsChallenge {
        address challenger;
        uint256 timestamp;
        string reason;
        uint256 challengeStake;
        bool resolved;
        bool challengeUpheld;
    }

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

    uint256 public nextBondId = 1;
    mapping(uint256 => Bond) public bonds;
    mapping(uint256 => GlobalFlourishingMetrics[]) public bondMetrics;
    mapping(uint256 => Distribution[]) public bondDistributions;
    mapping(uint256 => uint256) public lastDistributedValue;

    address payable public humanTreasury;

    // Oracle integration
    mapping(address => OracleSource) public oracles;
    address[] public registeredOracles;

    // AI-to-AI verification
    mapping(uint256 => AIVerification[]) public bondAIVerifications;
    mapping(address => uint256) public aiCompanyVerificationCount;

    // O(1) counters
    mapping(uint256 => uint256) public verificationConfirmCount;
    mapping(uint256 => uint256) public verificationRejectCount;

    // Metrics challenges
    mapping(uint256 => MetricsChallenge[]) public bondChallenges;
    mapping(uint256 => uint256) public activeChallengeCount;
    uint256 public constant MIN_CHALLENGE_STAKE = 0.1 ether;

    // Profit locking thresholds
    uint256 public constant SUFFERING_THRESHOLD = 4000;
    uint256 public constant LOW_INCLUSION_THRESHOLD = 4000;

    // Verification thresholds
    uint256 public constant MIN_AI_VERIFICATIONS = 2;
    uint256 public constant MIN_ORACLE_TRUST_SCORE = 7000;

    // V3: Oracle-required stake threshold
    uint256 public oracleRequiredThreshold = 10 ether;
    mapping(uint256 => bool) public oracleConfirmedMetrics;

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

    event BondCreated(uint256 indexed bondId, address indexed aiCompany, string companyName, uint256 quarterlyRevenue, uint256 stakeAmount, address stakeToken, uint256 timestamp);
    event HumanTreasuryUpdated(address indexed previousTreasury, address indexed newTreasury);
    event MetricsSubmitted(uint256 indexed bondId, uint256 timestamp, uint256 globalFlourishingScore);
    event DistributionRequested(uint256 indexed bondId, address indexed aiCompany, uint256 requestedAt, uint256 availableAt);
    event BondDistributed(uint256 indexed bondId, address indexed aiCompany, uint256 humanShare, uint256 aiCompanyShare, int256 appreciation, string reason, uint256 timestamp);
    event ProfitsLocked(uint256 indexed bondId, string reason, uint256 timestamp);
    event OracleRegistered(address indexed oracleAddress, string sourceName, uint256 trustScore, uint256 timestamp);
    event AIVerificationSubmitted(uint256 indexed bondId, address indexed verifyingAI, bool confirmsMetrics, uint256 stakeAmount, uint256 timestamp);
    event MetricsChallenged(uint256 indexed bondId, address indexed challenger, string reason, uint256 challengeStake, uint256 timestamp);
    event ChallengeResolved(uint256 indexed bondId, uint256 indexed challengeIndex, bool challengeUpheld, uint256 timestamp);

    // V3 events
    event VerificationStakeReturned(uint256 indexed bondId, uint256 indexed verificationIndex, address indexed verifier, uint256 amount);
    event OracleMetricsConfirmed(uint256 indexed bondId, address indexed oracle, uint256 timestamp);
    event OracleRequiredThresholdUpdated(uint256 oldThreshold, uint256 newThreshold);
    event BondDistributionCycleCompleted(uint256 indexed bondId, uint256 cycleNumber);

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

    modifier onlyAICompany(uint256 bondId) {
        if (bonds[bondId].aiCompany != msg.sender) revert AB_OnlyAICompany();
        _;
    }

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

    // ============ Constructor ============

    constructor(address payable _humanTreasury) {
        if (_humanTreasury == address(0)) revert AB_ZeroAddress();
        humanTreasury = _humanTreasury;
        emit HumanTreasuryUpdated(address(0), _humanTreasury);
        missionEnforcementEnabled = false;
    }

    // ============ 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 AB_MissionNotSet();

        if (!missionEnforcement.isCompliantWithPrinciple(address(this), MissionEnforcement.CorePrinciple.AI_PROFIT_CAPS))
            revert AB_MissionProfitCaps();
        if (!missionEnforcement.isCompliantWithPrinciple(address(this), MissionEnforcement.CorePrinciple.COMMUNITY_CHALLENGES))
            revert AB_MissionCommunityChallenges();
        if (!missionEnforcement.isCompliantWithPrinciple(address(this), MissionEnforcement.CorePrinciple.PRIVACY_DEFAULT))
            revert AB_MissionPrivacy();
    }

    function setHumanTreasury(address payable _newTreasury) external onlyOwner {
        if (_newTreasury == address(0)) revert AB_ZeroAddress();
        address previous = humanTreasury;
        humanTreasury = _newTreasury;
        emit HumanTreasuryUpdated(previous, _newTreasury);
    }

    // ============ V3: Oracle Threshold Management ============

    function setOracleRequiredThreshold(uint256 newThreshold) external onlyOwner {
        uint256 old = oracleRequiredThreshold;
        oracleRequiredThreshold = newThreshold;
        emit OracleRequiredThresholdUpdated(old, newThreshold);
    }

    function oracleConfirmMetrics(uint256 bondId) external bondExists(bondId) {
        OracleSource storage oracle = oracles[msg.sender];
        if (!oracle.isActive) revert AB_OracleNotRegistered();
        if (oracle.trustScore < MIN_ORACLE_TRUST_SCORE) revert AB_OracleTrustLow();
        if (bondMetrics[bondId].length == 0) revert AB_NoMetricsToConfirm();

        oracleConfirmedMetrics[bondId] = true;
        emit OracleMetricsConfirmed(bondId, msg.sender, block.timestamp);
    }

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

    /**
     * @notice Create AI Accountability Bond with native token (ETH on Base, AVAX on Avalanche)
     * @dev AI company stakes 30% of quarterly revenue in native token
     */
    function createBond(
        string memory companyName,
        uint256 quarterlyRevenue
    ) external payable nonReentrant whenNotPaused returns (uint256) {
        _validateNonZero(msg.value, "Stake amount");
        if (msg.value < (quarterlyRevenue * 30) / 100) revert AB_InsufficientStake();
        if (bytes(companyName).length == 0) revert AB_CompanyNameRequired();
        if (bytes(companyName).length > 100) revert AB_CompanyNameTooLong();

        uint256 bondId = nextBondId++;

        bonds[bondId] = Bond({
            bondId: bondId,
            aiCompany: msg.sender,
            companyName: companyName,
            quarterlyRevenue: quarterlyRevenue,
            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);

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

    /**
     * @notice Create AI Accountability Bond with an ERC20 token (WETH, WAVAX, USDC, etc.)
     * @dev AI company stakes 30% of quarterly revenue in an accepted ERC20 token.
     *      Caller must approve this contract for `stakeAmount` before calling.
     *
     * Mission Alignment: Partners on Avalanche can stake WETH without bridging.
     * Partners on Base can stake WAVAX without bridging. No friction, no barriers.
     *
     * @param companyName AI company name
     * @param quarterlyRevenue Reported quarterly revenue (stake must be >= 30%)
     * @param token ERC20 token address (must be on acceptedTokens whitelist)
     * @param stakeAmount Amount of ERC20 tokens to stake
     */
    function createBondWithToken(
        string memory companyName,
        uint256 quarterlyRevenue,
        address token,
        uint256 stakeAmount
    ) external nonReentrant whenNotPaused returns (uint256) {
        _validateNonZero(stakeAmount, "Stake amount");
        if (stakeAmount < (quarterlyRevenue * 30) / 100) revert AB_InsufficientStake();
        if (bytes(companyName).length == 0) revert AB_CompanyNameRequired();
        if (bytes(companyName).length > 100) revert AB_CompanyNameTooLong();
        uint256 bondId = nextBondId++;

        bonds[bondId] = Bond({
            bondId: bondId,
            aiCompany: msg.sender,
            companyName: companyName,
            quarterlyRevenue: quarterlyRevenue,
            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);

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

    /**
     * @notice Submit global human flourishing metrics
     * @dev Measured globally, not per-company. Works with ZERO employment.
     */
    function submitMetrics(
        uint256 bondId,
        uint256 incomeDistributionScore,
        uint256 povertyRateScore,
        uint256 healthOutcomesScore,
        uint256 mentalHealthScore,
        uint256 educationAccessScore,
        uint256 purposeAgencyScore
    ) external onlyAICompany(bondId) bondExists(bondId) whenNotPaused {
        _validateScore(incomeDistributionScore, "Income distribution score");
        _validateScore(povertyRateScore, "Poverty rate score");
        _validateScore(healthOutcomesScore, "Health outcomes score");
        _validateScore(mentalHealthScore, "Mental health score");
        _validateScore(educationAccessScore, "Education access score");
        _validateScore(purposeAgencyScore, "Purpose/agency score");

        // V3: New metrics submission resets oracle confirmation
        oracleConfirmedMetrics[bondId] = false;

        bondMetrics[bondId].push(GlobalFlourishingMetrics({
            timestamp: block.timestamp,
            incomeDistributionScore: incomeDistributionScore,
            povertyRateScore: povertyRateScore,
            healthOutcomesScore: healthOutcomesScore,
            mentalHealthScore: mentalHealthScore,
            educationAccessScore: educationAccessScore,
            purposeAgencyScore: purposeAgencyScore
        }));

        uint256 flourishingScore = globalFlourishingScore(bondId);
        emit MetricsSubmitted(bondId, block.timestamp, flourishingScore);
    }

    function registerOracle(
        address oracleAddress,
        string memory sourceName,
        uint256 trustScore
    ) external onlyOwner {
        _validateAddress(oracleAddress, "Oracle address");
        if (bytes(sourceName).length == 0 || bytes(sourceName).length > 100) revert AB_SourceNameInvalid();
        _validateScore(trustScore, "Trust score");

        if (!oracles[oracleAddress].isActive) {
            registeredOracles.push(oracleAddress);
        }

        oracles[oracleAddress] = OracleSource({
            oracleAddress: oracleAddress,
            sourceName: sourceName,
            isActive: true,
            registeredAt: block.timestamp,
            trustScore: trustScore
        });

        emit OracleRegistered(oracleAddress, sourceName, trustScore, block.timestamp);
    }

    /**
     * @notice AI company verifies another AI's metrics
     * @dev V3: Verification stakes tracked and refundable. Stakes are always in native token.
     */
    function submitAIVerification(
        uint256 bondId,
        bool confirmsMetrics,
        string memory notes
    ) external payable bondExists(bondId) whenNotPaused {
        Bond storage bond = bonds[bondId];
        if (msg.sender == bond.aiCompany) revert AB_CannotVerifyOwn();
        _validateNonZero(msg.value, "Verification stake");
        if (bytes(notes).length == 0 || bytes(notes).length > 500) revert AB_NotesInvalid();

        bondAIVerifications[bondId].push(AIVerification({
            bondId: bondId,
            verifyingAI: msg.sender,
            verifyingCompanyName: "",
            timestamp: block.timestamp,
            confirmsMetrics: confirmsMetrics,
            notes: notes,
            stakeAmount: msg.value,
            stakeReturned: false
        }));

        if (confirmsMetrics) {
            verificationConfirmCount[bondId]++;
        } else {
            verificationRejectCount[bondId]++;
        }

        aiCompanyVerificationCount[msg.sender]++;

        emit AIVerificationSubmitted(bondId, msg.sender, confirmsMetrics, msg.value, block.timestamp);
    }

    /**
     * @notice V3: Return verification stakes to honest verifiers after distribution
     */
    function settleVerificationStake(
        uint256 bondId,
        uint256 verificationIndex
    ) external nonReentrant {
        if (verificationIndex >= bondAIVerifications[bondId].length) revert AB_VerificationNotExist();

        AIVerification storage verification = bondAIVerifications[bondId][verificationIndex];
        if (verification.stakeReturned) revert AB_StakeAlreadySettled();
        if (verification.stakeAmount == 0) revert AB_NoStakeToSettle();

        Bond storage bond = bonds[bondId];
        if (bond.distributionCount == 0) revert AB_NoBondDistribution();

        verification.stakeReturned = true;

        if (verification.confirmsMetrics) {
            (bool success, ) = payable(verification.verifyingAI).call{value: verification.stakeAmount}("");
            if (!success) revert AB_VerificationRefundFailed();
            emit VerificationStakeReturned(bondId, verificationIndex, verification.verifyingAI, verification.stakeAmount);
        } else {
            _accrueProtocolFunds("Incorrect verification rejection", verification.stakeAmount);
        }
    }

    /**
     * @notice Challenge suspicious metrics
     */
    function challengeMetrics(
        uint256 bondId,
        string memory reason
    ) external payable bondExists(bondId) whenNotPaused {
        if (msg.value < MIN_CHALLENGE_STAKE) revert AB_InsufficientChallengeStake();
        if (bytes(reason).length < 10 || bytes(reason).length > 500) revert AB_ReasonInvalid();

        bondChallenges[bondId].push(MetricsChallenge({
            challenger: msg.sender,
            timestamp: block.timestamp,
            reason: reason,
            challengeStake: msg.value,
            resolved: false,
            challengeUpheld: false
        }));

        activeChallengeCount[bondId]++;

        emit MetricsChallenged(bondId, msg.sender, reason, msg.value, block.timestamp);
    }

    /**
     * @notice Resolve metrics challenge
     * @dev V3 FIX C-03: Challenge penalty from yield pool, not generic balance.
     */
    function resolveChallenge(
        uint256 bondId,
        uint256 challengeIndex,
        bool upheld
    ) external onlyOwner nonReentrant {
        if (challengeIndex >= bondChallenges[bondId].length) revert AB_ChallengeNotExist();
        MetricsChallenge storage challenge = bondChallenges[bondId][challengeIndex];
        if (challenge.resolved) revert AB_ChallengeAlreadyResolved();

        address payable challengerAddr = payable(challenge.challenger);
        uint256 stakeAmount = challenge.challengeStake;

        challenge.resolved = true;
        challenge.challengeUpheld = upheld;

        if (activeChallengeCount[bondId] > 0) {
            activeChallengeCount[bondId]--;
        }

        if (upheld) {
            uint256 penalty = stakeAmount;
            if (yieldPool < penalty) revert AB_InsufficientYieldPenalty();
            yieldPool -= penalty;

            uint256 totalPayout = stakeAmount + penalty;
            (bool success, ) = challengerAddr.call{value: totalPayout}("");
            if (!success) revert AB_ChallengePayoutFailed();
        } else {
            _accrueProtocolFunds("Rejected challenge stake", stakeAmount);
        }

        emit ChallengeResolved(bondId, challengeIndex, upheld, block.timestamp);
    }

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

        // V3 FIX M-02: Large bonds require oracle-confirmed metrics
        if (bond.stakeAmount >= oracleRequiredThreshold) {
            if (!oracleConfirmedMetrics[bondId]) revert AB_OracleRequired();
        }

        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 V3.1: Payouts route to correct asset type (native or ERC20) automatically
     */
    function distributeBond(uint256 bondId)
        external
        nonReentrant
        whenNotPaused
        onlyAICompany(bondId)
        bondExists(bondId)
    {
        _requireMissionCompliance();
        Bond storage bond = bonds[bondId];
        if (!bond.distributionPending) revert AB_MustRequestFirst();
        if (block.timestamp < bond.distributionRequestedAt + DISTRIBUTION_TIMELOCK)
            revert AB_TimelockNotExpiredDist();

        bond.distributionPending = false;

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

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

        (bool locked, string memory lockReason) = shouldLockProfits(bondId);

        uint256 humanShare;
        uint256 aiCompanyShare;
        string memory reason;

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

            _useYieldPool(bondId, absAppreciation);
            if (locked) {
                humanShare = absAppreciation;
                aiCompanyShare = 0;
                reason = lockReason;
                emit ProfitsLocked(bondId, lockReason, block.timestamp);
            } else {
                humanShare = (absAppreciation + 1) / 2;
                aiCompanyShare = absAppreciation - humanShare;
                reason = "Global human flourishing improving";
            }
        } else {
            uint256 absDepreciation = uint256(-appreciation);
            _useYieldPool(bondId, absDepreciation);

            humanShare = absDepreciation;
            aiCompanyShare = 0;
            reason = "Depreciation compensation - humans suffering";

            if (locked) {
                emit ProfitsLocked(bondId, lockReason, block.timestamp);
            }
        }

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

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

        // V3.1: Transfer using correct asset type
        if (aiCompanyShare > 0) {
            _transferStake(bondId, payable(bond.aiCompany), aiCompanyShare);
        }

        if (humanShare > 0) {
            if (humanTreasury == address(0)) revert AB_HumanTreasuryNotSet();
            _transferStake(bondId, humanTreasury, humanShare);
        }

        emit BondDistributed(bondId, bond.aiCompany, humanShare, aiCompanyShare, appreciation, reason, block.timestamp);
        emit BondDistributionCycleCompleted(bondId, bond.distributionCount);
    }

    /**
     * @notice Deactivate a bond (voluntary exit)
     * @dev V3.1: Returns stake in correct asset type
     */
    function deactivateBond(uint256 bondId) external onlyAICompany(bondId) bondExists(bondId) nonReentrant {
        Bond storage bond = bonds[bondId];
        if (bond.distributionPending) revert AB_CannotDeactivatePending();

        bond.active = false;
        _updateTotalActiveBondValue(totalActiveBondValue - bond.stakeAmount);

        // V3.1: Return stake in the original asset type
        _transferStake(bondId, payable(bond.aiCompany), bond.stakeAmount);
    }

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

    function globalFlourishingScore(uint256 bondId) public view bondExists(bondId) returns (uint256) {
        GlobalFlourishingMetrics[] storage metrics = bondMetrics[bondId];
        if (metrics.length == 0) return 5000;

        GlobalFlourishingMetrics storage latest = metrics[metrics.length - 1];

        return (
            latest.incomeDistributionScore +
            latest.povertyRateScore +
            latest.healthOutcomesScore +
            latest.mentalHealthScore +
            latest.educationAccessScore +
            latest.purposeAgencyScore
        ) / 6;
    }

    function inclusionMultiplier(uint256 bondId) public view bondExists(bondId) returns (uint256) {
        GlobalFlourishingMetrics[] storage metrics = bondMetrics[bondId];
        if (metrics.length == 0) return 100;

        GlobalFlourishingMetrics storage latest = metrics[metrics.length - 1];
        uint256 inclusionScore = (latest.educationAccessScore + latest.purposeAgencyScore) / 2;

        if (inclusionScore >= 7000) return 150 + ((inclusionScore - 7000) / 60);
        if (inclusionScore >= 4000) return 100 + ((inclusionScore - 4000) / 60);
        return 50 + (inclusionScore / 80);
    }

    function hasDecliningTrend(uint256 bondId) public view bondExists(bondId) returns (bool) {
        GlobalFlourishingMetrics[] storage metrics = bondMetrics[bondId];
        if (metrics.length < 2) return false;

        uint256 current = globalFlourishingScore(bondId);
        uint256 previous = (
            metrics[metrics.length - 2].incomeDistributionScore +
            metrics[metrics.length - 2].povertyRateScore +
            metrics[metrics.length - 2].healthOutcomesScore +
            metrics[metrics.length - 2].mentalHealthScore +
            metrics[metrics.length - 2].educationAccessScore +
            metrics[metrics.length - 2].purposeAgencyScore
        ) / 6;

        return current < previous;
    }

    function timeMultiplier(uint256 bondId) public view bondExists(bondId) returns (uint256) {
        Bond storage bond = bonds[bondId];
        uint256 age = block.timestamp - bond.createdAt;
        uint256 yearsElapsed = age / 31536000;

        if (yearsElapsed < 1) return 100;
        if (yearsElapsed < 3) return 100 + (yearsElapsed * 50);
        return 200 + ((yearsElapsed - 3) * 50);
    }

    function calculateBondValue(uint256 bondId) public view bondExists(bondId) returns (uint256) {
        Bond storage bond = bonds[bondId];

        uint256 flourishing = globalFlourishingScore(bondId);
        uint256 inclusion = inclusionMultiplier(bondId);
        uint256 time = timeMultiplier(bondId);

        uint256 temp1 = bond.stakeAmount * flourishing;
        uint256 temp2 = temp1 * inclusion;
        uint256 temp3 = temp2 * time;

        return temp3 / 50000000;
    }

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

    function verificationQualityScore(uint256 bondId) public view bondExists(bondId) returns (uint256) {
        uint256 confirmCount = verificationConfirmCount[bondId];
        uint256 rejectCount = verificationRejectCount[bondId];
        uint256 activeChallenges = activeChallengeCount[bondId];

        int256 score = 5000;

        if (confirmCount >= 2) {
            score += 2000;
        } else if (confirmCount == 1) {
            score += 1000;
        }

        if (rejectCount > 0) {
            score -= int256(rejectCount * 3000);
        }

        if (activeChallenges > 0) {
            score -= int256(activeChallenges * 2000);
        }

        if (score < 0) return 0;
        if (score > 10000) return 10000;
        return uint256(score);
    }

    function shouldLockProfits(uint256 bondId) public view bondExists(bondId) returns (bool, string memory) {
        uint256 flourishing = globalFlourishingScore(bondId);

        if (flourishing < SUFFERING_THRESHOLD) {
            return (true, "Humans suffering");
        }

        if (hasDecliningTrend(bondId)) {
            return (true, "Declining human flourishing trend");
        }

        GlobalFlourishingMetrics[] storage metrics = bondMetrics[bondId];
        if (metrics.length > 0) {
            GlobalFlourishingMetrics storage latest = metrics[metrics.length - 1];
            uint256 inclusionScore = (latest.educationAccessScore + latest.purposeAgencyScore) / 2;
            if (inclusionScore < LOW_INCLUSION_THRESHOLD) {
                return (true, "Low inclusion - AI replacing without reskilling");
            }
        }

        uint256 verificationScore = verificationQualityScore(bondId);
        if (verificationScore < 3000) {
            return (true, "Failed verification - metrics disputed by peers");
        }

        return (false, "");
    }

    // ============ View Functions ============

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

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

    function getLatestMetrics(uint256 bondId) external view returns (GlobalFlourishingMetrics memory) {
        if (bondMetrics[bondId].length == 0) revert AB_NoMetricsSubmitted();
        return bondMetrics[bondId][bondMetrics[bondId].length - 1];
    }

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

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

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

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

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

    function getAIVerifications(uint256 bondId, uint256 offset, uint256 limit)
        external view returns (AIVerification[] memory page)
    {
        AIVerification[] storage items = bondAIVerifications[bondId];
        uint256 len = items.length;
        if (offset >= len || limit == 0) return new AIVerification[](0);
        uint256 end = offset + limit;
        if (end > len) end = len;
        page = new AIVerification[](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]; }
    }

    function getLatestDistribution(uint256 bondId) external view returns (Distribution memory) {
        if (bondDistributions[bondId].length == 0) revert AB_NoDistributionsYet();
        return bondDistributions[bondId][bondDistributions[bondId].length - 1];
    }
}
