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

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {InstrumentRegistry} from "./InstrumentRegistry.sol";
import {ReserveCommitmentStore} from "./ReserveCommitmentStore.sol";
import {OMNLCircuitBreaker} from "./OMNLCircuitBreaker.sol";
import {PolicyMath} from "./PolicyMath.sol";

/**
 * @title ComplianceCore
 * @notice Aggregates on-chain supply + attested reserves; enforces policy; emits audit events.
 * @dev Integrated minters call assertCanMintM0/M1 before minting.
 */
contract ComplianceCore {
    InstrumentRegistry public immutable registry;
    ReserveCommitmentStore public immutable reserves;
    OMNLCircuitBreaker public immutable breakers;

    event ComplianceSnapshot(
        bytes32 indexed lineId,
        uint256 s0,
        uint256 s1,
        uint256 r,
        uint256 validUntil,
        bytes32 evidenceHash,
        bytes32 merkleRoot,
        bool m0Ok,
        bool m1Ok,
        bool attestationStale,
        bool policyOk,
        bool reportingCompliant
    );
    error ComplianceBlocked(bytes32 lineId, string reason);
    error UnknownLine(bytes32 lineId);

    constructor(address registry_, address reserves_, address breakers_) {
        require(registry_ != address(0) && reserves_ != address(0) && breakers_ != address(0), "ComplianceCore: zero");
        registry = InstrumentRegistry(registry_);
        reserves = ReserveCommitmentStore(reserves_);
        breakers = OMNLCircuitBreaker(breakers_);
    }

    function _loadSupplies(bytes32 lineId) internal view returns (uint256 s0, uint256 s1) {
        InstrumentRegistry.Line memory line = registry.getLine(lineId);
        if (line.tokenM0 == address(0)) revert UnknownLine(lineId);
        s0 = IERC20(line.tokenM0).totalSupply();
        s1 = IERC20(line.tokenM1).totalSupply();
    }

    function _stale(uint256 validUntil) internal view returns (bool) {
        return block.timestamp > validUntil;
    }

    /**
     * @notice Full deterministic compliance view for dashboards and off-chain API.
     * @dev reportingCompliant is false when attestation is stale or breakers trip, even if pure math (policyOk) holds.
     */
    function getCompliance(bytes32 lineId)
        external
        view
        returns (
            uint256 s0,
            uint256 s1,
            uint256 r,
            uint256 validUntil,
            bytes32 evidenceHash,
            bytes32 merkleRoot,
            uint256 minR,
            uint256 maxS1,
            bool m0Ok,
            bool m1Ok,
            bool attestationStale,
            bool policyOk,
            bool operational,
            bool reportingCompliant
        )
    {
        (s0, s1) = _loadSupplies(lineId);
        ReserveCommitmentStore.Commitment memory c = reserves.getCommitment(lineId);
        r = c.R;
        validUntil = c.validUntil;
        evidenceHash = c.evidenceHash;
        merkleRoot = c.merkleRoot;
        minR = PolicyMath.minReservesForM0(s0);
        maxS1 = PolicyMath.maxM1ForM0(s0);
        attestationStale = _stale(validUntil);
        m0Ok = PolicyMath.m0BackingOk(s0, r);
        m1Ok = PolicyMath.m1ExpansionOk(s0, s1);
        policyOk = PolicyMath.isCompliant(s0, s1, r);
        operational = breakers.isLineOperational(lineId);
        reportingCompliant = policyOk && !attestationStale && operational;
    }

    /// @notice Reverts if global/line pause or policy would fail after minting delta M0.
    function assertCanMintM0(bytes32 lineId, uint256 mintAmount) external view {
        if (!breakers.isLineOperational(lineId)) revert ComplianceBlocked(lineId, "paused");
        InstrumentRegistry.Line memory line = registry.getLine(lineId);
        if (line.tokenM0 == address(0)) revert UnknownLine(lineId);
        if (!line.active) revert ComplianceBlocked(lineId, "inactive");

        ReserveCommitmentStore.Commitment memory c = reserves.getCommitment(lineId);
        if (breakers.enforceStaleBlockM0() && _stale(c.validUntil)) {
            revert ComplianceBlocked(lineId, "stale_attestation");
        }

        uint256 s0 = IERC20(line.tokenM0).totalSupply();
        if (!PolicyMath.canMintM0(s0, mintAmount, c.R)) {
            revert ComplianceBlocked(lineId, "m0_backing");
        }
    }

    /// @notice Reverts if global/line pause or M1 cap exceeded after mint.
    function assertCanMintM1(bytes32 lineId, uint256 mintAmount) external view {
        if (!breakers.isLineOperational(lineId)) revert ComplianceBlocked(lineId, "paused");
        InstrumentRegistry.Line memory line = registry.getLine(lineId);
        if (line.tokenM0 == address(0)) revert UnknownLine(lineId);
        if (!line.active) revert ComplianceBlocked(lineId, "inactive");

        ReserveCommitmentStore.Commitment memory c = reserves.getCommitment(lineId);
        if (breakers.enforceStaleBlockM1() && _stale(c.validUntil)) {
            revert ComplianceBlocked(lineId, "stale_attestation");
        }

        uint256 s0 = IERC20(line.tokenM0).totalSupply();
        uint256 s1 = IERC20(line.tokenM1).totalSupply();
        if (!PolicyMath.canMintM1(s0, s1, mintAmount)) {
            revert ComplianceBlocked(lineId, "m1_cap");
        }
    }

    /// @notice Permissionless heartbeat for indexers — emits current snapshot (costs gas for caller).
    function emitComplianceSnapshot(bytes32 lineId) external {
        InstrumentRegistry.Line memory line = registry.getLine(lineId);
        if (line.tokenM0 == address(0)) revert UnknownLine(lineId);

        uint256 s0 = IERC20(line.tokenM0).totalSupply();
        uint256 s1 = IERC20(line.tokenM1).totalSupply();
        ReserveCommitmentStore.Commitment memory c = reserves.getCommitment(lineId);
        bool stale_ = _stale(c.validUntil);
        bool m0Ok = PolicyMath.m0BackingOk(s0, c.R);
        bool m1Ok = PolicyMath.m1ExpansionOk(s0, s1);
        bool policyOk = PolicyMath.isCompliant(s0, s1, c.R);
        bool operational = breakers.isLineOperational(lineId);
        bool reporting = policyOk && !stale_ && operational;

        emit ComplianceSnapshot(
            lineId,
            s0,
            s1,
            c.R,
            c.validUntil,
            c.evidenceHash,
            c.merkleRoot,
            m0Ok,
            m1Ok,
            stale_,
            policyOk,
            reporting
        );
    }
}
