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

import "../VWAPTracker.sol";
import "../libraries/UniswapMath.sol";
import { LiquidityAmounts } from "@aperture/uni-v3-lib/LiquidityAmounts.sol";
import "@aperture/uni-v3-lib/TickMath.sol";
import { Math } from "@openzeppelin/utils/math/Math.sol";
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";

/**
 * @title ThreePositionStrategy
 * @notice Abstract contract implementing the three-position liquidity strategy (Floor, Anchor, Discovery)
 * @dev Provides the core logic for anti-arbitrage asymmetric slippage profile
 *
 * Three-Position Strategy:
 * - ANCHOR: Near current price, fast price discovery (width computed from anchorWidth param provided by Optimizer)
 * - DISCOVERY: Borders anchor, captures fees (11000 tick spacing)
 * - FLOOR: Deep liquidity at VWAP-adjusted prices
 *
 * The asymmetric slippage profile prevents profitable arbitrage by making
 * buys progressively more expensive while sells remain liquid
 */
abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker {
    using Math for uint256;

    /// @notice Tick spacing for the pool (base spacing)
    int24 internal constant TICK_SPACING = 200;
    /// @notice Discovery spacing (3x current price in ticks - 11000 ticks = ~3x price)
    int24 internal constant DISCOVERY_SPACING = 11_000;
    /// @notice Minimum discovery depth multiplier
    uint128 internal constant MIN_DISCOVERY_DEPTH = 200;
    /// @notice Maximum safe anchorWidth: ensures 34 * MAX_ANCHOR_WIDTH * TICK_SPACING / 100 fits in int24
    /// @dev With TICK_SPACING=200: 34 * 1233 * 200 = 8,384,400 ≤ int24 max (8,388,607).
    ///      anchorWidth=1234 produces 8,391,200 which overflows int24 and reverts in Solidity 0.8.
    uint24 internal constant MAX_ANCHOR_WIDTH = 1233;

    /// @notice The three liquidity position types
    enum Stage {
        FLOOR,
        ANCHOR,
        DISCOVERY
    }

    /// @notice Structure representing a liquidity position
    struct TokenPosition {
        uint128 liquidity;
        int24 tickLower;
        int24 tickUpper;
    }

    /// @notice Parameters for position strategy
    struct PositionParams {
        uint256 capitalInefficiency;
        uint256 anchorShare;
        uint24 anchorWidth;
        uint256 discoveryDepth;
    }

    /// @notice Storage for the three positions
    mapping(Stage => TokenPosition) public positions;

    /// @notice Deprecated — was floor high-water mark. Kept for storage layout compatibility.
    int24 public __deprecated_floorHighWaterMark;

    /// @notice Events for tracking ETH abundance/scarcity scenarios
    event EthScarcity(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, int24 vwapTick);
    event EthAbundance(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, int24 vwapTick);
    /// @notice Abstract functions that must be implemented by inheriting contracts

    function _getKraikenToken() internal view virtual returns (address);
    function _getWethToken() internal view virtual returns (address);
    function _isToken0Weth() internal view virtual returns (bool);
    function _mintPosition(Stage stage, int24 tickLower, int24 tickUpper, uint128 liquidity) internal virtual;
    function _getEthBalance() internal view virtual returns (uint256);
    function _getOutstandingSupply() internal view virtual returns (uint256);

    /// @notice Sets all three positions according to the asymmetric slippage strategy
    /// @param currentTick The current market tick
    /// @param params Position parameters from optimizer
    function _setPositions(int24 currentTick, PositionParams memory params) internal {
        uint256 ethBalance = _getEthBalance();

        // Calculate floor ETH allocation (75% to 95% of total)
        uint256 floorEthBalance = (19 * ethBalance / 20) - (2 * params.anchorShare * ethBalance / 10 ** 19);

        // Step 1: Set ANCHOR position (shallow liquidity for fast price movement)
        (uint256 pulledKraiken, uint128 anchorLiquidity) = _setAnchorPosition(currentTick, ethBalance - floorEthBalance, params);

        // Step 2: Set DISCOVERY position (depends on anchor's liquidity)
        uint256 discoveryAmount = _setDiscoveryPosition(currentTick, anchorLiquidity, params);

        // Step 3: Set FLOOR position (deep liquidity, uses VWAP for historical memory)
        _setFloorPosition(currentTick, floorEthBalance, pulledKraiken, discoveryAmount, params);
    }

    /// @notice Sets the anchor position around current price (shallow liquidity)
    /// @param currentTick Current market tick
    /// @param anchorEthBalance ETH allocated to anchor position
    /// @param params Position parameters
    /// @return pulledKraiken Amount of KRAIKEN pulled for this position
    /// @return anchorLiquidity The liquidity amount for the anchor position
    function _setAnchorPosition(
        int24 currentTick,
        uint256 anchorEthBalance,
        PositionParams memory params
    )
        internal
        returns (uint256 pulledKraiken, uint128 anchorLiquidity)
    {
        // Compute anchor spacing from anchorWidth param (range enforcement is the Optimizer's responsibility)
        int24 anchorSpacing = TICK_SPACING + (34 * int24(params.anchorWidth) * TICK_SPACING / 100);

        int24 tickLower = _clampToTickSpacing(currentTick - anchorSpacing, TICK_SPACING);
        int24 tickUpper = _clampToTickSpacing(currentTick + anchorSpacing, TICK_SPACING);

        uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(currentTick);
        uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
        uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);

        bool token0isWeth = _isToken0Weth();

        if (token0isWeth) {
            anchorLiquidity = LiquidityAmounts.getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, anchorEthBalance);
            pulledKraiken = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, anchorLiquidity);
        } else {
            anchorLiquidity = LiquidityAmounts.getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, anchorEthBalance);
            pulledKraiken = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, anchorLiquidity);
        }

        _mintPosition(Stage.ANCHOR, tickLower, tickUpper, anchorLiquidity);
    }

    /// @notice Sets the discovery position (deep edge liquidity)
    /// @param currentTick Current market tick (normalized to tick spacing)
    /// @param anchorLiquidity Liquidity amount from anchor position
    /// @param params Position parameters
    /// @return discoveryAmount Amount of KRAIKEN used for discovery
    function _setDiscoveryPosition(int24 currentTick, uint128 anchorLiquidity, PositionParams memory params) internal returns (uint256 discoveryAmount) {
        currentTick = currentTick / TICK_SPACING * TICK_SPACING;
        bool token0isWeth = _isToken0Weth();

        // Calculate anchor spacing (same as in anchor position)
        int24 anchorSpacing = TICK_SPACING + (34 * int24(params.anchorWidth) * TICK_SPACING / 100);

        int24 tickLower = _clampToTickSpacing(token0isWeth ? currentTick - DISCOVERY_SPACING - anchorSpacing : currentTick + anchorSpacing, TICK_SPACING);
        int24 tickUpper = _clampToTickSpacing(token0isWeth ? currentTick - anchorSpacing : currentTick + DISCOVERY_SPACING + anchorSpacing, TICK_SPACING);

        uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
        uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);

        // Calculate discovery liquidity to ensure X times more liquidity per tick than anchor
        // Discovery should have 2x to 10x more liquidity per tick (not just total liquidity)
        uint256 discoveryMultiplier = 200 + (800 * params.discoveryDepth / 10 ** 18);

        // Calculate anchor width in ticks
        int24 anchorWidth = 2 * anchorSpacing;

        // Adjust for width difference: discovery liquidity = anchor liquidity * multiplier * (discovery width / anchor width)
        uint128 liquidity = uint128(uint256(anchorLiquidity) * discoveryMultiplier * uint256(int256(DISCOVERY_SPACING)) / (100 * uint256(int256(anchorWidth))));

        // Calculate discoveryAmount for floor position calculation
        if (token0isWeth) {
            discoveryAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
        } else {
            discoveryAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
        }

        _mintPosition(Stage.DISCOVERY, tickLower, tickUpper, liquidity);
    }

    /// @notice Sets the floor position using VWAP for historical price memory (deep edge liquidity)
    /// @dev Floor position placement depends on ETH scarcity vs abundance:
    ///      - Scarcity: Floor moves to extreme ticks (140k+) where KRAIKEN is very cheap
    ///      - Abundance: Floor placed near VWAP-adjusted price
    ///      Extreme floor positions are CORRECT behavior protecting protocol solvency
    /// @param currentTick Current market tick
    /// @param floorEthBalance ETH allocated to floor position (75% of total)
    /// @param pulledKraiken KRAIKEN amount from anchor position
    /// @param discoveryAmount KRAIKEN amount from discovery position
    /// @param params Position parameters including capital inefficiency
    function _setFloorPosition(
        int24 currentTick,
        uint256 floorEthBalance,
        uint256 pulledKraiken,
        uint256 discoveryAmount,
        PositionParams memory params
    )
        internal
    {
        bool token0isWeth = _isToken0Weth();

        // Calculate outstanding supply after position minting
        uint256 outstandingSupply = _getOutstandingSupply();
        outstandingSupply -= pulledKraiken;
        outstandingSupply -= (outstandingSupply >= discoveryAmount) ? discoveryAmount : outstandingSupply;

        // Floor placement: max of (scarcity, VWAP mirror, clamp) toward KRK-cheap side.
        // VWAP mirror uses distance from VWAP as floor distance — during selling, price moves
        // away from VWAP so floor retreats automatically. No sell-pressure detection needed.
        (int24 vwapTick, bool isScarcity) = _computeFloorTickWithSignal(currentTick, floorEthBalance, outstandingSupply, token0isWeth, params);

        // Emit ETH reserve event for indexers (Ponder, subgraphs)
        {
            uint256 vwapX96 = getAdjustedVWAP(params.capitalInefficiency);
            if (isScarcity) {
                emit EthScarcity(currentTick, floorEthBalance, outstandingSupply, vwapX96, vwapTick);
            } else {
                emit EthAbundance(currentTick, floorEthBalance, outstandingSupply, vwapX96, vwapTick);
            }
        }

        // Normalize and create floor position
        vwapTick = _clampToTickSpacing(vwapTick, TICK_SPACING);
        int24 floorTick = _clampToTickSpacing(token0isWeth ? vwapTick + TICK_SPACING : vwapTick - TICK_SPACING, TICK_SPACING);

        // Use planned floor ETH balance, but fallback to remaining if insufficient
        uint256 remainingEthBalance = _getEthBalance();
        uint256 actualFloorEthBalance = (remainingEthBalance >= floorEthBalance) ? floorEthBalance : remainingEthBalance;

        uint128 liquidity;
        if (token0isWeth) {
            // floor leg sits entirely above current tick when WETH is token0, so budget is token0
            liquidity =
                LiquidityAmounts.getLiquidityForAmount0(TickMath.getSqrtRatioAtTick(vwapTick), TickMath.getSqrtRatioAtTick(floorTick), actualFloorEthBalance);
        } else {
            liquidity =
                LiquidityAmounts.getLiquidityForAmount1(TickMath.getSqrtRatioAtTick(vwapTick), TickMath.getSqrtRatioAtTick(floorTick), actualFloorEthBalance);
        }

        _mintPosition(Stage.FLOOR, token0isWeth ? vwapTick : floorTick, token0isWeth ? floorTick : vwapTick, liquidity);
    }

    /// @notice Computes floor tick from three signals: scarcity, VWAP mirror, and anti-overlap clamp.
    /// @dev Takes the one furthest into KRK-cheap territory (highest tick when token0isWeth, lowest when not).
    /// @return floorTarget The computed floor tick
    /// @return isScarcity True if scarcity signal dominated (ETH scarcity), false if mirror/clamp (ETH abundance)
    function _computeFloorTickWithSignal(
        int24 currentTick,
        uint256 floorEthBalance,
        uint256 outstandingSupply,
        bool token0isWeth,
        PositionParams memory params
    )
        internal
        view
        returns (int24 floorTarget, bool isScarcity)
    {
        // 1. Scarcity tick: at what price can our ETH buy back the adjusted supply?
        uint256 balancedCapital = (7 * outstandingSupply / 10) + (outstandingSupply * params.capitalInefficiency / 10 ** 18);
        int24 scarcityTick = currentTick;
        if (outstandingSupply > 0 && floorEthBalance > 0) {
            scarcityTick = _tickAtPrice(token0isWeth, balancedCapital, floorEthBalance);
        }

        // 2. Mirror tick: VWAP distance mirrored to KRK-cheap side
        //    Uses adjusted VWAP (CI controls distance → CI is the risk lever).
        int24 mirrorTick = currentTick;
        {
            uint256 vwapX96 = getAdjustedVWAP(params.capitalInefficiency);
            if (vwapX96 > 0) {
                // vwapX96 >> 32 converts Q96 → Q64 (ABDKMath64x64) for _tickAtPriceRatio.
                // Skip mirror tick if the shifted value exceeds int128 range to prevent
                // overflow in the int128 cast (#622). mirrorTick stays at currentTick.
                uint256 priceRatioX64 = vwapX96 >> 32;
                if (priceRatioX64 <= uint256(uint128(type(int128).max))) {
                    int24 rawVwapTick = _tickAtPriceRatio(int128(int256(priceRatioX64)));
                    rawVwapTick = token0isWeth ? -rawVwapTick : rawVwapTick;
                    int24 vwapDistance = currentTick - rawVwapTick;
                    if (vwapDistance < 0) vwapDistance = -vwapDistance;
                    mirrorTick = token0isWeth ? currentTick + vwapDistance : currentTick - vwapDistance;
                }
            }
        }

        // 3. Clamp tick: minimum distance (anti-overlap with anchor)
        int24 anchorSpacing = TICK_SPACING + (34 * int24(params.anchorWidth) * TICK_SPACING / 100);
        int24 clampTick = token0isWeth ? currentTick + anchorSpacing : currentTick - anchorSpacing;

        // Take the one furthest into KRK-cheap territory
        // Track whether scarcity signal dominates (for event emission)
        isScarcity = true;
        if (token0isWeth) {
            floorTarget = scarcityTick;
            if (mirrorTick > floorTarget) {
                floorTarget = mirrorTick;
                isScarcity = false;
            }
            if (clampTick > floorTarget) {
                floorTarget = clampTick;
                isScarcity = false;
            }
        } else {
            floorTarget = scarcityTick;
            if (mirrorTick < floorTarget) {
                floorTarget = mirrorTick;
                isScarcity = false;
            }
            if (clampTick < floorTarget) {
                floorTarget = clampTick;
                isScarcity = false;
            }
        }
    }
}
