// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;

import { IOptimizer, OptimizerInput, BEAR_CAPITAL_INEFFICIENCY, BEAR_ANCHOR_SHARE, BEAR_ANCHOR_WIDTH, BEAR_DISCOVERY_DEPTH } from "./IOptimizer.sol";
import { Kraiken } from "./Kraiken.sol";
import { Stake } from "./Stake.sol";

import { TickMath } from "@aperture/uni-v3-lib/TickMath.sol";
import { Initializable } from "@openzeppelin/proxy/utils/Initializable.sol";
import { UUPSUpgradeable } from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol";
import { Math } from "@openzeppelin/utils/math/Math.sol";

// ---------------------------------------------------------------------------
// Dyadic rational interface — Push3's native number format.
// Represents: mantissa × 2^(-shift).
// _toDyadic wraps an on-chain value with shift=0 (value == mantissa).
// ---------------------------------------------------------------------------

// Minimal interface for VWAPTracker (slots 2-4 computation)
interface IVWAPTracker {
    function getVWAP() external view returns (uint256);
}

// Minimal interface for Uniswap V3 pool (slots 2-4, 6 computation)
interface IUniswapV3PoolSlot0 {
    function slot0()
        external
        view
        returns (
            uint160 sqrtPriceX96,
            int24 tick,
            uint16 observationIndex,
            uint16 observationCardinality,
            uint16 observationCardinalityNext,
            uint8 feeProtocol,
            bool unlocked
        );
}

// Minimal interface for pool TWAP observations (slots 3-4 computation)
interface IUniswapV3PoolObserve {
    function observe(uint32[] calldata secondsAgos)
        external
        view
        returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);
}

// Minimal interface for LiquidityManager position data (slot 6 computation)
interface ILiquidityManagerPositions {
    function positions(uint8 stage) external view returns (uint128 liquidity, int24 tickLower, int24 tickUpper);
}

/**
 * @title Optimizer
 * @notice Calculates liquidity parameters for the LiquidityManager using an
 *         8-slot dyadic rational input interface (Push3's native format).
 *
 * @dev Upgradeable (UUPS). The core logic lives in `calculateParams`, which is
 *      a pure function taking an OptimizerInput[8] array. Future upgrades may
 *      replace `calculateParams` with a transpiled Push3 program via the
 *      evolution pipeline (#544, #545, #546).
 *
 * Input slots (all values in [0, 1e18] — uniform range makes evolution feasible):
 *   0  percentageStaked  0..1e18  % of supply staked (Stake.getPercentageStaked())
 *   1  averageTaxRate    0..1e18  Normalized tax rate (Stake.getAverageTaxRate())
 *   2  pricePosition     0..1e18  Current price vs VWAP ± PRICE_BOUND_TICKS.
 *                                  0 = at lower bound, 0.5e18 = at VWAP, 1e18 = at upper bound.
 *                                  (0 if vwapTracker/pool not configured or no VWAP data yet)
 *   3  volatility        0..1e18  Normalized recent price volatility: |shortTwap - longTwap|
 *                                  ticks / MAX_VOLATILITY_TICKS, capped at 1e18.
 *                                  (0 if pool not configured or insufficient TWAP history)
 *   4  momentum          0..1e18  Price trend: 0 = strongly falling, 0.5e18 = flat,
 *                                  1e18 = strongly rising. Derived from short vs long TWAP.
 *                                  (0 if pool not configured or insufficient TWAP history)
 *   5  timeSinceRecenter 0..1e18  Normalized time since last recenter.
 *                                  0 = just recentered, 1e18 = MAX_STALE_SECONDS elapsed.
 *                                  (0 if recordRecenter has never been called)
 *   6  utilizationRate   0..1e18  1e18 if current tick is within anchor position range,
 *                                  0 otherwise. (0 if liquidityManager/pool not configured)
 *   7  reserved          0        Future use.
 *
 * Four optimizer outputs (0..1e18 fractions unless noted):
 *   capitalInefficiency  capital buffer level
 *   anchorShare          fraction of non-floor ETH in anchor
 *   anchorWidth          anchor position width (tick units, uint24)
 *   discoveryDepth       discovery liquidity density
 */
contract Optimizer is Initializable, UUPSUpgradeable, IOptimizer {
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    Kraiken private kraiken;
    Stake private stake;

    // ---- Extended data sources for input slots 2-6 ----
    // These are optional; unset addresses leave the corresponding slots as 0.
    address public vwapTracker; // slots 2-4 source (VWAPTracker)
    address public pool; // slots 2-4, 6 source (Uniswap V3 pool)
    uint256 public lastRecenterTimestamp; // slot 5 source (updated via recordRecenter)
    address public recenterRecorder; // authorized to call recordRecenter
    address public liquidityManager; // slot 6 source (LiquidityManager positions)
    bool public token0isWeth; // true when WETH is token0 in the pool (flips tick direction)

    // ---- Normalization constants ----

    /// @notice Half-width in ticks for pricePosition normalization.
    ///         pricePosition = 0 at (vwapTick - PRICE_BOUND_TICKS), 1e18 at (vwapTick + PRICE_BOUND_TICKS).
    ///         11 000 ticks ≈ the discovery position half-width (3× price from anchor).
    int256 internal constant PRICE_BOUND_TICKS = 11_000;

    /// @notice Maximum tick divergence (shortTwap vs longTwap) that maps to full volatility (1e18).
    ///         1 000 ticks ≈ 10% price swing.
    uint256 internal constant MAX_VOLATILITY_TICKS = 1000;

    /// @notice Maximum tick trend signal (shortTwap - longTwap) for momentum saturation.
    ///         1 000 ticks ≈ 10% price trend.
    int256 internal constant MAX_MOMENTUM_TICKS = 1000;

    /// @notice Time (seconds) beyond which timeSinceRecenter saturates at 1e18. 86 400 = 1 day.
    uint256 internal constant MAX_STALE_SECONDS = 86_400;

    /// @notice Short TWAP window for volatility / momentum (5 minutes = same as price-stability check).
    uint32 internal constant SHORT_TWAP_WINDOW = 300;

    /// @notice Long TWAP window for volatility / momentum baseline (30 minutes).
    uint32 internal constant LONG_TWAP_WINDOW = 1800;

    /// @dev Reverts if the caller is not the admin.
    error UnauthorizedAccount(address account);

    /// @dev Gas budget forwarded to calculateParams via staticcall.
    ///      Evolved programs that exceed this are treated as crashes — same outcome
    ///      as a revert — and getLiquidityParams() returns bear defaults instead.
    ///      500 000 gives ~33x headroom over the current seed (~15 k gas) while
    ///      preventing unbounded growth from blocking recenter().
    ///
    ///      Note (EIP-150 / 63-64 rule): the outer getLiquidityParams() call must
    ///      arrive with at least ⌈500_000 × 64/63⌉ ≈ 507_937 gas for the inner
    ///      staticcall to actually receive 500 000.  Callers with exactly 500–508 k
    ///      gas will see a spurious bear-defaults fallback.  This is not a practical
    ///      concern from recenter(), which always has abundant gas.
    uint256 internal constant CALCULATE_PARAMS_GAS_LIMIT = 500_000;

    /**
     * @notice Initialize the Optimizer.
     * @param _kraiken The address of the Kraiken token.
     * @param _stake The address of the Stake contract.
     */
    function initialize(address _kraiken, address _stake) public initializer {
        // Set the admin for upgradeability (using ERC1967Upgrade _changeAdmin)
        _changeAdmin(msg.sender);
        kraiken = Kraiken(_kraiken);
        stake = Stake(_stake);
    }

    modifier onlyAdmin() {
        _checkAdmin();
        _;
    }

    function _checkAdmin() internal view virtual {
        if (_getAdmin() != msg.sender) {
            revert UnauthorizedAccount(msg.sender);
        }
    }

    function _authorizeUpgrade(address newImplementation) internal override onlyAdmin { }

    // ---- Data-source configuration (admin only) ----

    /**
     * @notice Configure optional on-chain data sources for input slots 2-6.
     * @param _vwapTracker       VWAPTracker contract address (slots 2-4); zero = disabled.
     * @param _pool              Uniswap V3 pool address (slots 2-4, 6); zero = disabled.
     * @param _liquidityManager  LiquidityManager address (slot 6); zero = disabled.
     * @param _token0isWeth      True when WETH is token0 in the pool.  Needed to correctly
     *                           orient tick-based indicators (pricePosition, volatility, momentum).
     */
    function setDataSources(address _vwapTracker, address _pool, address _liquidityManager, bool _token0isWeth) external onlyAdmin {
        vwapTracker = _vwapTracker;
        pool = _pool;
        liquidityManager = _liquidityManager;
        token0isWeth = _token0isWeth;
    }

    /**
     * @notice Set the address authorized to call recordRecenter.
     * @param _recorder  The LiquidityManager or other authorized address.
     */
    function setRecenterRecorder(address _recorder) external onlyAdmin {
        recenterRecorder = _recorder;
    }

    /**
     * @notice Record a recenter event for slot 5 (timeSinceLastRecenter).
     * @dev Called by the LiquidityManager (or recenterRecorder) after each recenter.
     */
    function recordRecenter() external {
        if (msg.sender != recenterRecorder && msg.sender != _getAdmin()) {
            revert UnauthorizedAccount(msg.sender);
        }
        lastRecenterTimestamp = block.timestamp;
    }

    // ---- Dyadic rational helpers ----

    /**
     * @notice Wrap an integer as a dyadic rational with shift=0.
     *         value = mantissa × 2^(-0) = mantissa.
     */
    function _toDyadic(int256 value) internal pure returns (OptimizerInput memory) {
        return OptimizerInput({ mantissa: value, shift: 0 });
    }

    /**
     * @notice Safe bear-mode defaults returned when calculateParams exceeds its
     *         gas budget or reverts.
     * @dev Constants defined in IOptimizer.sol, shared with LiquidityManager.recenter().
     */
    function _bearDefaults() internal pure returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) {
        return (BEAR_CAPITAL_INEFFICIENCY, BEAR_ANCHOR_SHARE, BEAR_ANCHOR_WIDTH, BEAR_DISCOVERY_DEPTH);
    }

    // ---- Normalization helpers ----

    /**
     * @notice Convert a Q96 price (price * 2^96) to the corresponding Uniswap V3 tick.
     *
     * @dev    VWAP is stored as `price * 2^96` where `price = sqrtPriceX96^2 / 2^96`.
     *         Inverting: `sqrtPriceX96 = sqrt(vwapX96) << 48`.
     *         Integer sqrt introduces at most ±1 ULP error in sqrtPriceX96, which
     *         translates to at most ±1 tick error — acceptable for normalization.
     *
     *         Overflow guard: for prices near TickMath extremes, `sqrt(vwapX96) << 48`
     *         can approach or exceed uint160 max.  We clamp to TickMath's valid range.
     *
     * @param  vwapX96  VWAP in Q96 price format (token1/token0 × 2^96).
     * @return vwapTick The Uniswap V3 tick closest to the VWAP price.
     */
    function _vwapToTick(uint256 vwapX96) internal pure returns (int24 vwapTick) {
        uint256 sqrtVwap = Math.sqrt(vwapX96); // = sqrt(price) * 2^48
        uint256 shifted = sqrtVwap << 48; // ≈ sqrtPriceX96 = sqrt(price) * 2^96
        uint160 sqrtPriceX96;
        if (shifted >= uint256(TickMath.MAX_SQRT_RATIO)) {
            sqrtPriceX96 = TickMath.MAX_SQRT_RATIO - 1;
        } else if (shifted < uint256(TickMath.MIN_SQRT_RATIO)) {
            sqrtPriceX96 = TickMath.MIN_SQRT_RATIO;
        } else {
            sqrtPriceX96 = uint160(shifted);
        }
        vwapTick = TickMath.getTickAtSqrtRatio(sqrtPriceX96);
    }

    // ---- Core computation ----

    /**
     * @notice Calculates the sentiment based on the average tax rate and the percentage staked.
     * @param averageTaxRate The average tax rate (as returned by the Stake contract).
     * @param percentageStaked The percentage (in 1e18 precision) of the authorized stake that is currently staked.
     * @return sentimentValue A value in the range 0 to 1e18 where 1e18 represents the worst sentiment.
     */
    function calculateSentiment(uint256 averageTaxRate, uint256 percentageStaked) public pure returns (uint256 sentimentValue) {
        // Ensure percentageStaked doesn't exceed 100%
        require(percentageStaked <= 1e18, "Invalid percentage staked");

        // deltaS is the "slack" available below full staking
        uint256 deltaS = 1e18 - percentageStaked;

        if (percentageStaked > 92e16) {
            // If more than 92% of the authorized stake is in use, the sentiment drops rapidly.
            // Penalty is computed as: (deltaS^3 * averageTaxRate) / (20 * 1e48)
            uint256 penalty = (deltaS * deltaS * deltaS * averageTaxRate) / (20 * 1e48);
            sentimentValue = penalty / 2;
        } else {
            // For lower staked percentages, sentiment decreases roughly linearly.
            // Ensure we don't underflow if percentageStaked approaches 92%
            uint256 scaledStake = (percentageStaked * 1e18) / (92e16);
            uint256 baseSentiment = scaledStake >= 1e18 ? 0 : 1e18 - scaledStake;
            // Apply a penalty based on the average tax rate.
            if (averageTaxRate <= 1e16) {
                sentimentValue = baseSentiment;
            } else if (averageTaxRate <= 5e16) {
                uint256 ratePenalty = ((averageTaxRate - 1e16) * baseSentiment) / (4e16);
                sentimentValue = baseSentiment > ratePenalty ? baseSentiment - ratePenalty : 0;
            } else {
                // For very high tax rates, sentiment is maximally poor.
                sentimentValue = 1e18;
            }
        }
        return sentimentValue;
    }

    /**
     * @notice Returns the current sentiment.
     * @return sentiment A number (with 1e18 precision) representing the staker sentiment.
     */
    function getSentiment() external view returns (uint256 sentiment) {
        uint256 percentageStaked = stake.getPercentageStaked();
        uint256 averageTaxRate = stake.getAverageTaxRate();
        sentiment = calculateSentiment(averageTaxRate, percentageStaked);
    }

    /**
     * @notice Calculates the optimal anchor width based on staking metrics.
     * @param percentageStaked The percentage of tokens staked (0 to 1e18)
     * @param averageTaxRate The average tax rate across all stakers (0 to 1e18)
     * @return anchorWidth The calculated anchor width (10 to 80)
     */
    function _calculateAnchorWidth(uint256 percentageStaked, uint256 averageTaxRate) internal pure returns (uint24) {
        // Base width: 40% is our neutral starting point
        int256 baseWidth = 40;

        // Staking adjustment: -20% to +20% based on staking percentage
        int256 stakingAdjustment = 20 - int256(percentageStaked * 40 / 1e18);

        // Tax rate adjustment: -10% to +30% based on average tax rate
        int256 taxAdjustment = int256(averageTaxRate * 40 / 1e18) - 10;

        // Combine all adjustments
        int256 totalWidth = baseWidth + stakingAdjustment + taxAdjustment;

        // Clamp to safe bounds (10 to 80)
        if (totalWidth < 10) {
            return 10;
        }
        if (totalWidth > 80) {
            return 80;
        }

        return uint24(uint256(totalWidth));
    }

    /**
     * @notice Pure computation of all four liquidity parameters from 8 dyadic inputs.
     *
     * @dev This is the transpilation target: future versions of this function will be
     *      generated from evolved Push3 programs via the transpiler. The current
     *      implementation uses slots 0 (percentageStaked) and 1 (averageTaxRate);
     *      slots 2-7 are available to evolved programs that use the normalized indicators.
     *
     * @param inputs  8 dyadic rational slots. For shift == 0 (via _toDyadic), value == mantissa.
     *   inputs[0].mantissa = percentageStaked  (0..1e18)
     *   inputs[1].mantissa = averageTaxRate    (0..1e18)
     *   inputs[2].mantissa = pricePosition     (0..1e18)
     *   inputs[3].mantissa = volatility        (0..1e18)
     *   inputs[4].mantissa = momentum          (0..1e18)
     *   inputs[5].mantissa = timeSinceRecenter (0..1e18)
     *   inputs[6].mantissa = utilizationRate   (0..1e18)
     *   inputs[7]          = reserved (0)
     *
     * @return capitalInefficiency  Capital buffer level (0..1e18). CI=0 is safest.
     * @return anchorShare          Fraction of non-floor ETH in anchor (0..1e18).
     * @return anchorWidth          Anchor position width in tick units (uint24).
     * @return discoveryDepth       Discovery liquidity density (0..1e18).
     */
    function calculateParams(OptimizerInput[8] memory inputs)
        public
        pure
        virtual
        returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth)
    {
        // Guard against non-zero shift, negative mantissa, and overflow.
        // All 8 slots must be in [0, 1e18] — the uniform dyadic range.
        // shift is reserved for future use; uint256() cast silently wraps negatives.
        for (uint256 k; k < 8; k++) {
            require(inputs[k].shift == 0, "shift not yet supported");
            require(inputs[k].mantissa >= 0, "negative mantissa");
            require(inputs[k].mantissa <= 1e18, "mantissa overflow");
        }
        // Extract slots 0 and 1 (shift=0 enforced above — mantissa IS the value)
        uint256 percentageStaked = uint256(inputs[0].mantissa);
        uint256 averageTaxRate = uint256(inputs[1].mantissa);

        uint256 sentiment = calculateSentiment(averageTaxRate, percentageStaked);
        if (sentiment > 1e18) {
            sentiment = 1e18;
        }

        capitalInefficiency = 1e18 - sentiment;
        anchorShare = sentiment;
        anchorWidth = _calculateAnchorWidth(percentageStaked, averageTaxRate);
        discoveryDepth = sentiment;
    }

    /**
     * @notice Returns liquidity parameters for the LiquidityManager.
     *
     * @dev Populates the 8-slot dyadic input array with normalized indicators
     *      (all in [0, 1e18]) and delegates to calculateParams.  Normalization
     *      happens here so that evolved Push3 programs can reason about relative
     *      positions without dealing with raw Q96 prices or absolute ticks.
     *
     *      Slots populated:
     *        0  percentageStaked  always
     *        1  averageTaxRate    always
     *        2  pricePosition     when vwapTracker + pool configured and VWAP > 0
     *        3  volatility        when pool configured and TWAP history available
     *        4  momentum          when pool configured and TWAP history available
     *        5  timeSinceRecenter when recordRecenter has been called at least once
     *        6  utilizationRate   when liquidityManager + pool configured
     *        7  reserved          always 0
     *
     */
    /**
     * @notice Build the 8-slot normalized input array from on-chain data sources.
     * @dev Extracted so test harnesses can observe the computed inputs without
     *      duplicating normalization logic.  All slots are in [0, 1e18].
     */
    function _buildInputs() internal view returns (OptimizerInput[8] memory inputs) {
        // Slot 0: percentageStaked
        inputs[0] = _toDyadic(int256(stake.getPercentageStaked()));

        // Slot 1: averageTaxRate
        inputs[1] = _toDyadic(int256(stake.getAverageTaxRate()));

        // Slots 2-4 (pricePosition, volatility, momentum) and slot 6 (utilizationRate)
        // all require the pool address.  Read slot0 once and reuse.
        if (pool != address(0)) {
            (, int24 poolTick,,,,,) = IUniswapV3PoolSlot0(pool).slot0();

            // ---- Slot 2: pricePosition (also needs VWAP) ----
            if (vwapTracker != address(0)) {
                uint256 vwapX96 = IVWAPTracker(vwapTracker).getVWAP();
                if (vwapX96 > 0) {
                    // Convert pool tick to KRK-price space: higher tick = more expensive KRK.
                    // Uniswap convention: tick ↑ → token1 more expensive relative to token0.
                    // If token0=WETH (token1=KRK): tick ↑ → KRK/WETH ↑ → KRK more expensive.
                    //   No sign flip needed — pool tick already tracks KRK price direction.
                    // If token0=KRK  (token1=WETH): tick ↑ → WETH/KRK ↑ → KRK cheaper → negate.
                    // Same convention as LiquidityManager._priceAtTick(token0isWeth ? -tick : tick).
                    int24 currentAdjTick = token0isWeth ? poolTick : -poolTick;

                    // vwapTick in same adjusted (KRK-price) space
                    int24 vwapAdjTick = _vwapToTick(vwapX96);

                    // Slot 2: pricePosition — where is current price vs VWAP ± PRICE_BOUND_TICKS?
                    // 0 = at lower bound (vwap − bound), 0.5e18 = at VWAP, 1e18 = at upper bound.
                    int256 delta = int256(currentAdjTick) - int256(vwapAdjTick);
                    int256 shifted = delta + PRICE_BOUND_TICKS; // map to [0, 2*bound]
                    if (shifted < 0) shifted = 0;
                    if (shifted > 2 * PRICE_BOUND_TICKS) shifted = 2 * PRICE_BOUND_TICKS;
                    inputs[2] = _toDyadic(int256(uint256(shifted) * 1e18 / uint256(2 * PRICE_BOUND_TICKS)));
                }
            }

            // ---- Slots 3-4: volatility and momentum from pool TWAP ----
            // Independent of VWAP — only the pool oracle is required.
            // Fails gracefully if the pool lacks sufficient observation history.
            {
                uint32[] memory secondsAgo = new uint32[](3);
                secondsAgo[0] = LONG_TWAP_WINDOW; // 1800 s — long baseline
                secondsAgo[1] = SHORT_TWAP_WINDOW; // 300 s  — recent
                secondsAgo[2] = 0; // now
                try IUniswapV3PoolObserve(pool).observe(secondsAgo) returns (int56[] memory tickCumulatives, uint160[] memory) {
                    int24 longTwap = int24((tickCumulatives[2] - tickCumulatives[0]) / int56(int32(LONG_TWAP_WINDOW)));
                    int24 shortTwap = int24((tickCumulatives[2] - tickCumulatives[1]) / int56(int32(SHORT_TWAP_WINDOW)));

                    // Adjust both TWAP ticks to KRK-price space (same sign convention)
                    int24 longAdj = token0isWeth ? longTwap : -longTwap;
                    int24 shortAdj = token0isWeth ? shortTwap : -shortTwap;
                    int256 twapDelta = int256(shortAdj) - int256(longAdj);

                    // Slot 3: volatility = |shortTwap − longTwap| / MAX_VOLATILITY_TICKS
                    {
                        uint256 absDelta = twapDelta >= 0 ? uint256(twapDelta) : uint256(-twapDelta);
                        uint256 vol = absDelta >= MAX_VOLATILITY_TICKS ? 1e18 : absDelta * 1e18 / MAX_VOLATILITY_TICKS;
                        inputs[3] = _toDyadic(int256(vol));
                    }

                    // Slot 4: momentum — 0.5e18 flat, 1e18 strongly rising, 0 strongly falling
                    {
                        int256 momentum;
                        if (twapDelta >= MAX_MOMENTUM_TICKS) {
                            momentum = int256(1e18);
                        } else if (twapDelta <= -MAX_MOMENTUM_TICKS) {
                            momentum = 0;
                        } else {
                            momentum = int256(5e17) + twapDelta * int256(5e17) / MAX_MOMENTUM_TICKS;
                        }
                        inputs[4] = _toDyadic(momentum);
                    }
                } catch {
                    // Insufficient TWAP history — leave slots 3-4 as 0
                }
            }

            // Slot 6: utilizationRate — 1e18 if current tick is within anchor range, else 0.
            // Stage.ANCHOR == 1 in the ThreePositionStrategy enum.
            if (liquidityManager != address(0)) {
                (, int24 anchorLower, int24 anchorUpper) = ILiquidityManagerPositions(liquidityManager).positions(1);
                if (poolTick >= anchorLower && poolTick <= anchorUpper) {
                    inputs[6] = _toDyadic(int256(1e18));
                }
            }
        }

        // Slot 5: timeSinceRecenter normalized to [0, 1e18].
        // 0 = just recentered, 1e18 = MAX_STALE_SECONDS or more have elapsed.
        if (lastRecenterTimestamp > 0) {
            uint256 elapsed = block.timestamp - lastRecenterTimestamp;
            uint256 normalized = elapsed >= MAX_STALE_SECONDS ? 1e18 : elapsed * 1e18 / MAX_STALE_SECONDS;
            inputs[5] = _toDyadic(int256(normalized));
        }

        // Slot 7: reserved (0)
    }

    /**
     * @return capitalInefficiency Capital buffer level (0..1e18)
     * @return anchorShare         Fraction of non-floor ETH in anchor (0..1e18)
     * @return anchorWidth         Anchor position width in tick units (uint24)
     * @return discoveryDepth      Discovery liquidity density (0..1e18)
     */
    function getLiquidityParams()
        external
        view
        override
        returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth)
    {
        OptimizerInput[8] memory inputs = _buildInputs();

        // Call calculateParams with a fixed gas budget. Evolved programs that grow
        // too large hit the cap and fall back to bear defaults — preventing any
        // buggy or bloated optimizer from blocking recenter() with an OOG revert.
        (bool ok, bytes memory ret) = address(this).staticcall{ gas: CALCULATE_PARAMS_GAS_LIMIT }(abi.encodeCall(this.calculateParams, (inputs)));
        if (!ok) return _bearDefaults();
        // ABI encoding of (uint256, uint256, uint24, uint256) is exactly 128 bytes
        // (each value padded to 32 bytes).  A truncated return — e.g. from a
        // malformed evolved program — would cause abi.decode to revert; guard here
        // so all failure modes fall back via _bearDefaults().
        if (ret.length < 128) return _bearDefaults();
        (capitalInefficiency, anchorShare, anchorWidth, discoveryDepth) = abi.decode(ret, (uint256, uint256, uint24, uint256));
        // Clamp fraction outputs to [0, 1e18] so a buggy evolved program cannot
        // produce out-of-range values that confuse the LiquidityManager.
        // anchorWidth is already bounded by uint24 at the ABI level.
        if (capitalInefficiency > 1e18) capitalInefficiency = 1e18;
        if (anchorShare > 1e18) anchorShare = 1e18;
        if (discoveryDepth > 1e18) discoveryDepth = 1e18;
    }
}
