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

import {IERC20Supply} from "./interfaces/IERC20Supply.sol";
import {ICWTokenUsdFeedView, IUniV3PoolTokens} from "./interfaces/IMarketAnchor.sol";

/**
 * @title CWTokenMarketRegistry
 * @notice On-chain market anchor for mainnet cW* — face USD, published TWAP, x402 settlement mirror,
 *         and Chainlink-compatible per-token feeds for indexers that ingest custom oracles.
 * @dev Does not unilaterally control Etherscan UI; pairs with UniV3 liquidity + operator sync.
 */
contract CWTokenMarketRegistry {
    uint8 public constant PRICE_DECIMALS = 8;
    /// @dev Max deviation from faceValueUsdE8 on publishPrice (1500 = 15%) for USD-face cW* stables.
    uint16 public constant MAX_PEG_DEVIATION_BPS = 1500;

    address public admin;
    bool public paused;

    struct TokenConfig {
        bool registered;
        uint8 tokenDecimals;
        uint64 faceValueUsdE8;
        address uniV3Pool;
        bool cwIsToken0;
        uint64 publishedPriceUsdE8;
        uint256 publishedAt;
        uint80 roundId;
        uint256 settlementCount;
    }

    mapping(address => TokenConfig) public tokens;
    mapping(address => address) public tokenFeed;
    address[] public tokenList;

    event AdminChanged(address indexed newAdmin);
    event Paused();
    event Unpaused();
    event TokenRegistered(
        address indexed token,
        uint64 faceValueUsdE8,
        address indexed uniV3Pool,
        address indexed feed
    );
    event MarketPricePublished(
        address indexed token,
        uint80 roundId,
        uint64 priceUsdE8,
        uint256 timestamp,
        string source
    );
    event X402SettlementRecorded(
        address indexed token,
        address indexed payer,
        address indexed payTo,
        uint256 amountRaw,
        uint64 faceUsdE8,
        uint256 settlementChainId,
        bytes32 settlementTxHash,
        bytes32 resourceHash
    );

    modifier onlyAdmin() {
        require(msg.sender == admin, "only admin");
        _;
    }

    modifier whenNotPaused() {
        require(!paused, "paused");
        _;
    }

    constructor(address _admin) {
        require(_admin != address(0), "zero admin");
        admin = _admin;
    }

    function setAdmin(address newAdmin) external onlyAdmin {
        require(newAdmin != address(0), "zero admin");
        admin = newAdmin;
        emit AdminChanged(newAdmin);
    }

    function pause() external onlyAdmin {
        paused = true;
        emit Paused();
    }

    function unpause() external onlyAdmin {
        paused = false;
        emit Unpaused();
    }

    function registerToken(
        address token,
        uint8 tokenDecimals,
        uint64 faceValueUsdE8,
        address uniV3Pool,
        bool cwIsToken0,
        address feed
    ) external onlyAdmin whenNotPaused {
        require(token != address(0), "zero token");
        require(faceValueUsdE8 > 0, "zero face");
        require(feed != address(0), "zero feed");
        require(
            ICWTokenUsdFeedView(feed).registry() == address(this)
                && ICWTokenUsdFeedView(feed).token() == token,
            "feed mismatch"
        );
        if (uniV3Pool != address(0)) {
            address t0 = IUniV3PoolTokens(uniV3Pool).token0();
            address t1 = IUniV3PoolTokens(uniV3Pool).token1();
            require(t0 == token || t1 == token, "pool token");
        }

        if (tokens[token].registered) {
            require(tokens[token].tokenDecimals == tokenDecimals, "decimals locked");
        } else {
            tokenList.push(token);
        }

        tokens[token] = TokenConfig({
            registered: true,
            tokenDecimals: tokenDecimals,
            faceValueUsdE8: faceValueUsdE8,
            uniV3Pool: uniV3Pool,
            cwIsToken0: cwIsToken0,
            publishedPriceUsdE8: faceValueUsdE8,
            publishedAt: block.timestamp,
            roundId: 1,
            settlementCount: tokens[token].settlementCount
        });
        tokenFeed[token] = feed;

        emit TokenRegistered(token, faceValueUsdE8, uniV3Pool, feed);
        emit MarketPricePublished(token, 1, faceValueUsdE8, block.timestamp, "face_bootstrap");
    }

    function publishPrice(address token, uint64 priceUsdE8, string calldata source)
        external
        onlyAdmin
        whenNotPaused
    {
        TokenConfig storage t = _requireToken(token);
        require(priceUsdE8 > 0, "zero price");
        _enforcePegBand(t.faceValueUsdE8, priceUsdE8);
        t.roundId += 1;
        t.publishedPriceUsdE8 = priceUsdE8;
        t.publishedAt = block.timestamp;
        emit MarketPricePublished(token, t.roundId, priceUsdE8, block.timestamp, source);
    }

    function recordX402Settlement(
        address token,
        address payer,
        address payTo,
        uint256 amountRaw,
        uint64 faceUsdE8,
        uint256 settlementChainId,
        bytes32 settlementTxHash,
        bytes32 resourceHash
    ) external onlyAdmin whenNotPaused {
        TokenConfig storage t = _requireToken(token);
        require(payer != address(0) && payTo != address(0), "zero addr");
        require(amountRaw > 0, "zero amount");
        t.settlementCount += 1;
        emit X402SettlementRecorded(
            token,
            payer,
            payTo,
            amountRaw,
            faceUsdE8,
            settlementChainId,
            settlementTxHash,
            resourceHash
        );
    }

    function latestPriceUsdE8(address token) external view returns (uint64) {
        TokenConfig storage t = _requireToken(token);
        return t.publishedPriceUsdE8 != 0 ? t.publishedPriceUsdE8 : t.faceValueUsdE8;
    }

    /// @notice USD market cap with 8 decimals: priceUsdE8 * totalSupply / 10^tokenDecimals
    function onchainMarketCapUsdE8(address token) external view returns (uint256) {
        TokenConfig storage t = _requireToken(token);
        uint64 price = t.publishedPriceUsdE8 != 0 ? t.publishedPriceUsdE8 : t.faceValueUsdE8;
        uint256 supply = IERC20Supply(token).totalSupply();
        return (uint256(price) * supply) / (10 ** uint256(t.tokenDecimals));
    }

    function tokenCount() external view returns (uint256) {
        return tokenList.length;
    }

    function latestRoundDataForToken(address token)
        external
        view
        returns (uint80 roundId, int256 answer, uint256 updatedAt)
    {
        TokenConfig storage t = _requireToken(token);
        uint64 price = t.publishedPriceUsdE8 != 0 ? t.publishedPriceUsdE8 : t.faceValueUsdE8;
        return (t.roundId, int256(uint256(price)), t.publishedAt);
    }

    function _requireToken(address token) internal view returns (TokenConfig storage t) {
        t = tokens[token];
        require(t.registered, "unregistered");
    }

    function _enforcePegBand(uint64 faceUsdE8, uint64 priceUsdE8) internal pure {
        uint256 maxDev = (uint256(faceUsdE8) * MAX_PEG_DEVIATION_BPS) / 10_000;
        require(priceUsdE8 >= faceUsdE8 - maxDev && priceUsdE8 <= faceUsdE8 + maxDev, "peg band");
    }
}
