// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.30;

import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {IPartyInfo} from "../src/IPartyInfo.sol";
import {IPartyPlanner} from "../src/IPartyPlanner.sol";
import {IPartyPool} from "../src/IPartyPool.sol";
import {NativeWrapper} from "../src/NativeWrapper.sol";
import {PartyInfo} from "../src/PartyInfo.sol";
import {PartyPlanner} from "../src/PartyPlanner.sol";
import {PartyPoolInitCode, PartyPoolBalancedPairInitCode} from "../src/PartyPoolDeployer.sol";
import {PartyPoolMintImpl} from "../src/PartyPoolMintImpl.sol";
import {PartyPoolSwapImpl} from "../src/PartyPoolSwapImpl.sol";
import {WETH9} from "./WETH9.sol";
import {MockERC20} from "./MockERC20.sol";

library Deploy {
    address internal constant PROTOCOL_FEE_RECEIVER = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; // dev account #1
    uint256 internal constant PROTOCOL_FEE_PPM = 100_000; // 10%

    function newPartyPlanner() internal returns (IPartyPlanner) {
        NativeWrapper wrapper = new WETH9();
        return newPartyPlanner(address(this), wrapper);
    }

    function newPartyPlanner(address owner) internal returns (IPartyPlanner) {
        NativeWrapper wrapper = new WETH9();
        return newPartyPlanner(owner, wrapper);
    }

    function newPartyPlanner(address owner, NativeWrapper wrapper) internal returns (IPartyPlanner) {
        return new PartyPlanner(
            owner,
            wrapper,
            new PartyPoolSwapImpl(wrapper),
            new PartyPoolMintImpl(wrapper),
            new PartyPoolInitCode(),
            new PartyPoolBalancedPairInitCode(),
            PROTOCOL_FEE_PPM,
            PROTOCOL_FEE_RECEIVER
        );
    }

    function newPartyPool(
        string memory name_,
        string memory symbol_,
        IERC20[] memory tokens_,
        int128 _kappa,
        uint256 _swapFeePpm,
        uint256 _flashFeePpm,
        bool _stable,
        uint256 _initialBalance,
        uint256 _lpTokens
    ) internal returns (IPartyPool pool) {
        NativeWrapper wrapper = new WETH9();
        (pool,) = newPartyPool2(NPPArgs(name_, symbol_, tokens_, _kappa, _swapFeePpm, _flashFeePpm, wrapper, _stable, _initialBalance, _lpTokens));
    }

    function newPartyPool(
        string memory name_,
        string memory symbol_,
        IERC20[] memory tokens_,
        int128 _kappa,
        uint256 _swapFeePpm,
        uint256 _flashFeePpm,
        NativeWrapper wrapper,
        bool _stable,
        uint256 _initialBalance,
        uint256 _lpTokens
    ) internal returns (IPartyPool pool) {
        (pool,) = newPartyPool2(NPPArgs(name_, symbol_, tokens_, _kappa, _swapFeePpm, _flashFeePpm, wrapper, _stable, _initialBalance, _lpTokens));
    }


    function newPartyPool2(
        string memory name_,
        string memory symbol_,
        IERC20[] memory tokens_,
        int128 _kappa,
        uint256 _swapFeePpm,
        uint256 _flashFeePpm,
        bool _stable,
        uint256 _initialBalance,
        uint256 _lpTokens
    ) internal returns (IPartyPool pool, uint256 lpTokens) {
        NativeWrapper wrapper = new WETH9();
        return newPartyPool2(NPPArgs(name_, symbol_, tokens_, _kappa, _swapFeePpm, _flashFeePpm, wrapper, _stable, _initialBalance, _lpTokens));
    }

    /// @notice Deploy a pool using explicit per-token initial deposits (useful for non-uniform initial balances)
    function newPartyPoolWithDeposits(
        string memory name_,
        string memory symbol_,
        IERC20[] memory tokens_,
        int128 _kappa,
        uint256 _swapFeePpm,
        uint256 _flashFeePpm,
        bool _stable,
        uint256[] memory initialDeposits,
        uint256 _lpTokens
    ) internal returns (IPartyPool pool, uint256 lpTokens) {
        require(initialDeposits.length == tokens_.length, "mismatched deposits length");
        NativeWrapper wrapper = new WETH9();

        // Prepare planner and arrays
        NPPVars memory v = NPPVars(
            address(newPartyPlanner(address(this), wrapper)),
            new uint256[](tokens_.length),
            new uint256[](tokens_.length)
        );
        address self = address(this);

        // Build per-asset fee vector from scalar for tests
        for (uint256 i = 0; i < tokens_.length; i++) { v.feesArr[i] = _swapFeePpm; }

        // Mint/prepare the specified deposits for each token and approve the planner
        for (uint256 i = 0; i < tokens_.length; i++) {
            v.deposits[i] = initialDeposits[i];
            if (address(tokens_[i]) == address(wrapper)) {
                // Wrap native value and approve planner
                if (initialDeposits[i] > 0) {
                    argsWrapperDeposit(wrapper, v.planner, initialDeposits[i]);
                }
            } else {
                MockERC20 t = MockERC20(address(tokens_[i]));
                if (initialDeposits[i] > 0) {
                    t.mint(self, initialDeposits[i]);
                    t.approve(v.planner, initialDeposits[i]);
                }
            }
        }

        // Create pool with provided deposits
        (pool, lpTokens) = IPartyPlanner(v.planner).newPool(
            name_,
            symbol_,
            tokens_,
            _kappa,
            _swapFeePpm,
            _flashFeePpm,
            _stable,
            self,
            self,
            v.deposits,
            _lpTokens,
            0
        );
    }

    // Helper to deposit native wrapper value and approve planner (kept separate for clarity)
    function argsWrapperDeposit(NativeWrapper wrapper, address planner, uint256 amount) internal {
        if (amount == 0) return;
        wrapper.deposit{value: amount}();
        wrapper.approve(planner, amount);
    }

    struct NPPVars {
        address planner;
        uint256[] feesArr;
        uint256[] deposits;
    }

    struct NPPArgs {
        string name;
        string symbol;
        IERC20[] tokens;
        int128 kappa;
        uint256 swapFeePpm;
        uint256 flashFeePpm;
        NativeWrapper wrapper;
        bool stable;
        uint256 initialBalance;
        uint256 lpTokens;
    }

    function newPartyPool2( NPPArgs memory args ) internal returns (IPartyPool pool, uint256 lpTokens) {
        NPPVars memory v = NPPVars(
            address(newPartyPlanner(address(this), args.wrapper)),
            new uint256[](args.tokens.length),
            new uint256[](args.tokens.length)
        );
        address self = address(this);

        // Build per-asset fee vector from scalar for tests
        for (uint256 i = 0; i < args.tokens.length; i++) { v.feesArr[i] = args.swapFeePpm; }

        for (uint256 i = 0; i < args.tokens.length; i++) {
            if (address(args.tokens[i]) == address(args.wrapper)) {
                // Not a MockERC20. Wrap coins instead of minting.
                args.wrapper.deposit{value: args.initialBalance}();
                args.wrapper.approve(v.planner, args.initialBalance);
                v.deposits[i] = args.initialBalance;
            }
            else {
                MockERC20 t = MockERC20(address(args.tokens[i]));
                t.mint(self, args.initialBalance);
                t.approve(v.planner, args.initialBalance);
                v.deposits[i] = args.initialBalance;
            }
        }

        (pool, lpTokens) = IPartyPlanner(v.planner).newPool(
            args.name,
            args.symbol,
            args.tokens,
            args.kappa,
            args.swapFeePpm,
            args.flashFeePpm,
            args.stable,
            self,
            self,
            v.deposits,
            args.lpTokens,
            0
        );
    }


    function newInfo() internal returns (IPartyInfo) {
        NativeWrapper wrapper = new WETH9();
        return new PartyInfo(new PartyPoolSwapImpl(wrapper), new PartyPoolMintImpl(wrapper));
    }
}
