// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import { L2ToL1MessagePasser } from "src/L2/L2ToL1MessagePasser.sol";
import { SafeCall } from "src/libraries/SafeCall.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";

/// @title FeeVault
/// @notice The FeeVault contract contains the basic logic for the various different vault contracts
///         used to hold fee revenue generated by the L2 system.
abstract contract FeeVault {
    /// @notice Enum representing where the FeeVault withdraws funds to.
    /// @custom:value L1 FeeVault withdraws funds to L1.
    /// @custom:value L2 FeeVault withdraws funds to L2.
    enum WithdrawalNetwork {
        L1,
        L2
    }

    /// @notice Minimum balance before a withdrawal can be triggered.
    ///         Use the `minWithdrawalAmount()` getter as this is deprecated
    ///         and is subject to be removed in the future.
    /// @custom:legacy
    uint256 public immutable MIN_WITHDRAWAL_AMOUNT;

    /// @notice Account that will receive the fees. Can be located on L1 or L2.
    ///         Use the `recipient()` getter as this is deprecated
    ///         and is subject to be removed in the future.
    /// @custom:legacy
    address public immutable RECIPIENT;

    /// @notice Network which the recipient will receive fees on.
    ///         Use the `withdrawalNetwork()` getter as this is deprecated
    ///         and is subject to be removed in the future.
    /// @custom:legacy
    WithdrawalNetwork public immutable WITHDRAWAL_NETWORK;

    /// @notice The minimum gas limit for the FeeVault withdrawal transaction.
    uint32 internal constant WITHDRAWAL_MIN_GAS = 400_000;

    /// @notice Total amount of wei processed by the contract.
    uint256 public totalProcessed;

    /// @notice Reserve extra slots in the storage layout for future upgrades.
    uint256[48] private __gap;

    /// @notice Emitted each time a withdrawal occurs. This event will be deprecated
    ///         in favor of the Withdrawal event containing the WithdrawalNetwork parameter.
    /// @param value Amount that was withdrawn (in wei).
    /// @param to    Address that the funds were sent to.
    /// @param from  Address that triggered the withdrawal.
    event Withdrawal(uint256 value, address to, address from);

    /// @notice Emitted each time a withdrawal occurs.
    /// @param value             Amount that was withdrawn (in wei).
    /// @param to                Address that the funds were sent to.
    /// @param from              Address that triggered the withdrawal.
    /// @param withdrawalNetwork Network which the to address will receive funds on.
    event Withdrawal(uint256 value, address to, address from, WithdrawalNetwork withdrawalNetwork);

    /// @param _recipient           Wallet that will receive the fees.
    /// @param _minWithdrawalAmount Minimum balance for withdrawals.
    /// @param _withdrawalNetwork   Network which the recipient will receive fees on.
    constructor(address _recipient, uint256 _minWithdrawalAmount, WithdrawalNetwork _withdrawalNetwork) {
        RECIPIENT = _recipient;
        MIN_WITHDRAWAL_AMOUNT = _minWithdrawalAmount;
        WITHDRAWAL_NETWORK = _withdrawalNetwork;
    }

    /// @notice Allow the contract to receive ETH.
    receive() external payable { }

    /// @notice Minimum balance before a withdrawal can be triggered.
    function minWithdrawalAmount() public view returns (uint256 amount_) {
        amount_ = MIN_WITHDRAWAL_AMOUNT;
    }

    /// @notice Account that will receive the fees. Can be located on L1 or L2.
    function recipient() public view returns (address recipient_) {
        recipient_ = RECIPIENT;
    }

    /// @notice Network which the recipient will receive fees on.
    function withdrawalNetwork() public view returns (WithdrawalNetwork network_) {
        network_ = WITHDRAWAL_NETWORK;
    }

    /// @notice Triggers a withdrawal of funds to the fee wallet on L1 or L2.
    function withdraw() external {
        require(
            address(this).balance >= MIN_WITHDRAWAL_AMOUNT,
            "FeeVault: withdrawal amount must be greater than minimum withdrawal amount"
        );

        uint256 value = address(this).balance;
        totalProcessed += value;

        emit Withdrawal(value, RECIPIENT, msg.sender);
        emit Withdrawal(value, RECIPIENT, msg.sender, WITHDRAWAL_NETWORK);

        if (WITHDRAWAL_NETWORK == WithdrawalNetwork.L2) {
            bool success = SafeCall.send(RECIPIENT, value);
            require(success, "FeeVault: failed to send ETH to L2 fee recipient");
        } else {
            L2ToL1MessagePasser(payable(Predeploys.L2_TO_L1_MESSAGE_PASSER)).initiateWithdrawal{ value: value }({
                _target: RECIPIENT,
                _gasLimit: WITHDRAWAL_MIN_GAS,
                _data: hex""
            });
        }
    }
}
