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

// Forge
import { Script } from "forge-std/Script.sol";

// Scripts
import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol";
import { DeployUtils } from "scripts/libraries/DeployUtils.sol";

// Libraries
import { LibString } from "@solady/utils/LibString.sol";

// Interfaces
import { IProxy } from "interfaces/universal/IProxy.sol";
import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";

/// @title DeployDelayedWETH
contract DeployDelayedWETHInput is BaseDeployIO {
    /// Required inputs.
    string internal _release;
    address public _proxyAdmin;
    ISuperchainConfig public _superchainConfigProxy;
    address public _delayedWethImpl;
    address public _delayedWethOwner;
    uint256 public _delayedWethDelay;

    function set(bytes4 _sel, uint256 _value) public {
        if (_sel == this.delayedWethDelay.selector) {
            require(_value != 0, "DeployDelayedWETH: delayedWethDelay cannot be zero");
            _delayedWethDelay = _value;
        } else {
            revert("DeployDelayedWETH: unknown selector");
        }
    }

    function set(bytes4 _sel, address _value) public {
        if (_sel == this.proxyAdmin.selector) {
            require(_value != address(0), "DeployDelayedWETH: proxyAdmin cannot be zero address");
            _proxyAdmin = _value;
        } else if (_sel == this.superchainConfigProxy.selector) {
            require(_value != address(0), "DeployDelayedWETH: superchainConfigProxy cannot be zero address");
            _superchainConfigProxy = ISuperchainConfig(_value);
        } else if (_sel == this.delayedWethOwner.selector) {
            require(_value != address(0), "DeployDelayedWETH: delayedWethOwner cannot be zero address");
            _delayedWethOwner = _value;
        } else if (_sel == this.delayedWethImpl.selector) {
            _delayedWethImpl = _value;
        } else {
            revert("DeployDelayedWETH: unknown selector");
        }
    }

    function set(bytes4 _sel, string memory _value) public {
        if (_sel == this.release.selector) {
            require(!LibString.eq(_value, ""), "DeployDelayedWETH: release cannot be empty");
            _release = _value;
        } else {
            revert("DeployDelayedWETH: unknown selector");
        }
    }

    function release() public view returns (string memory) {
        require(!LibString.eq(_release, ""), "DeployDelayedWETH: release not set");
        return _release;
    }

    function proxyAdmin() public view returns (address) {
        require(_proxyAdmin != address(0), "DeployDelayedWETH: proxyAdmin not set");
        return _proxyAdmin;
    }

    function superchainConfigProxy() public view returns (ISuperchainConfig) {
        require(address(_superchainConfigProxy) != address(0), "DeployDisputeGame: superchainConfigProxy not set");
        return _superchainConfigProxy;
    }

    function delayedWethImpl() public view returns (address) {
        require(_delayedWethImpl != address(0), "DeployDelayedWETH: delayedWethImpl not set");
        return _delayedWethImpl;
    }

    function delayedWethOwner() public view returns (address) {
        require(_delayedWethOwner != address(0), "DeployDelayedWETH: delayedWethOwner not set");
        return _delayedWethOwner;
    }

    function delayedWethDelay() public view returns (uint256) {
        require(_delayedWethDelay != 0, "DeployDelayedWETH: delayedWethDelay not set");
        return _delayedWethDelay;
    }
}

/// @title DeployDelayedWETHOutput
contract DeployDelayedWETHOutput is BaseDeployIO {
    IDelayedWETH internal _delayedWethImpl;
    IDelayedWETH internal _delayedWethProxy;

    function set(bytes4 _sel, address _value) public {
        if (_sel == this.delayedWethImpl.selector) {
            require(_value != address(0), "DeployDelayedWETHOutput: delayedWethImpl cannot be zero address");
            _delayedWethImpl = IDelayedWETH(payable(_value));
        } else if (_sel == this.delayedWethProxy.selector) {
            require(_value != address(0), "DeployDelayedWETHOutput: delayedWethProxy cannot be zero address");
            _delayedWethProxy = IDelayedWETH(payable(_value));
        } else {
            revert("DeployDelayedWETHOutput: unknown selector");
        }
    }

    function checkOutput(DeployDelayedWETHInput _dwi) public {
        DeployUtils.assertValidContractAddress(address(_delayedWethImpl));
        DeployUtils.assertValidContractAddress(address(_delayedWethProxy));
        assertValidDeploy(_dwi);
    }

    function delayedWethImpl() public view returns (IDelayedWETH) {
        DeployUtils.assertValidContractAddress(address(_delayedWethImpl));
        return _delayedWethImpl;
    }

    function delayedWethProxy() public view returns (IDelayedWETH) {
        DeployUtils.assertValidContractAddress(address(_delayedWethProxy));
        return _delayedWethProxy;
    }

    function assertValidDeploy(DeployDelayedWETHInput _dwi) public {
        assertValidDelayedWethImpl(_dwi);
        assertValidDelayedWethProxy(_dwi);
    }

    function assertValidDelayedWethImpl(DeployDelayedWETHInput _dwi) internal {
        IProxy proxy = IProxy(payable(address(delayedWethProxy())));
        vm.prank(address(0));
        address impl = proxy.implementation();
        require(impl == address(delayedWethImpl()), "DWI-10");
        DeployUtils.assertInitialized({
            _contractAddress: address(delayedWethImpl()),
            _isProxy: false,
            _slot: 0,
            _offset: 0
        });
        require(delayedWethImpl().owner() == address(0), "DWI-20");
        require(delayedWethImpl().delay() == _dwi.delayedWethDelay(), "DWI-30");
        require(address(delayedWethImpl().config()) == address(0), "DWI-30");
    }

    function assertValidDelayedWethProxy(DeployDelayedWETHInput _dwi) internal {
        // Check as proxy.
        IProxy proxy = IProxy(payable(address(delayedWethProxy())));
        vm.prank(address(0));
        address admin = proxy.admin();
        require(admin == _dwi.proxyAdmin(), "DWP-10");

        // Check as implementation.
        DeployUtils.assertInitialized({
            _contractAddress: address(delayedWethProxy()),
            _isProxy: true,
            _slot: 0,
            _offset: 0
        });
        require(delayedWethProxy().owner() == _dwi.delayedWethOwner(), "DWP-20");
        require(delayedWethProxy().delay() == _dwi.delayedWethDelay(), "DWP-30");
        require(delayedWethProxy().config() == _dwi.superchainConfigProxy(), "DWP-40");
    }
}

/// @title DeployDelayedWETH
contract DeployDelayedWETH is Script {
    function run(DeployDelayedWETHInput _dwi, DeployDelayedWETHOutput _dwo) public {
        deployDelayedWethProxy(_dwi, _dwo);
        _dwo.checkOutput(_dwi);
    }

    function deployDelayedWethImpl(DeployDelayedWETHInput _dwi, DeployDelayedWETHOutput _dwo) internal {
        string memory release = _dwi.release();
        IDelayedWETH impl;

        address existingImplementation = _dwi.delayedWethImpl();
        if (existingImplementation != address(0)) {
            impl = IDelayedWETH(payable(existingImplementation));
        } else if (isDevelopRelease(release)) {
            vm.broadcast(msg.sender);
            impl = IDelayedWETH(
                DeployUtils.create1({
                    _name: "DelayedWETH",
                    _args: DeployUtils.encodeConstructor(
                        abi.encodeCall(IDelayedWETH.__constructor__, (_dwi.delayedWethDelay()))
                    )
                })
            );
        } else {
            revert(string.concat("DeployDelayedWETH: failed to deploy release ", release));
        }

        vm.label(address(impl), "DelayedWETHImpl");
        _dwo.set(_dwo.delayedWethImpl.selector, address(impl));
    }

    function deployDelayedWethProxy(DeployDelayedWETHInput _dwi, DeployDelayedWETHOutput _dwo) internal {
        vm.broadcast(msg.sender);
        IProxy proxy = IProxy(
            DeployUtils.create1({
                _name: "Proxy",
                _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (msg.sender)))
            })
        );

        deployDelayedWethImpl(_dwi, _dwo);
        IDelayedWETH impl = _dwo.delayedWethImpl();

        vm.startBroadcast(msg.sender);
        proxy.upgradeToAndCall(
            address(impl), abi.encodeCall(impl.initialize, (_dwi.delayedWethOwner(), _dwi.superchainConfigProxy()))
        );
        proxy.changeAdmin(_dwi.proxyAdmin());
        vm.stopBroadcast();

        vm.label(address(proxy), "DelayedWETHProxy");
        _dwo.set(_dwo.delayedWethProxy.selector, address(proxy));
    }

    // A release is considered a 'develop' release if it does not start with 'op-contracts'.
    function isDevelopRelease(string memory _release) internal pure returns (bool) {
        return !LibString.startsWith(_release, "op-contracts");
    }
}