// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: contracts/controller/Controllable.sol



//SPDX-License-Identifier: MIT
pragma solidity ~0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";

contract Controllable is Ownable {
    mapping(address => bool) public controllers;

    event ControllerChanged(address indexed controller, bool enabled);

    modifier onlyController() {
        require(
            controllers[msg.sender],
            "Controllable: Caller is not a controller"
        );
        _;
    }

    function setController(address controller, bool enabled) public onlyOwner {
        controllers[controller] = enabled;
        emit ControllerChanged(controller, enabled);
    }
}

// File: contracts/controller/NameMintingController.sol



//SPDX-License-Identifier: MIT
pragma solidity ~0.8.20;

import "../ens/NameWrapperDelegate.sol";

/**
 * @title Namespace Mint Controller
 * @author namespace.ninja
 * @notice Provides functionality for minting subnames.
 */
contract NameMintingController is Controllable {
    event SubdomainMinted(
        string label,
        bytes32 indexed parentNode,
        uint256 price,
        address subnameOwner,
        address paymentRecepient,
        address minter
    );

    NameWrapperDelegate public nameWrapperDelegate;

    address[] public registries;
    address public registryCalls;
    address[] public validationCalls;
    address public paymentCall;

    address public wethAddress;
    address public feeWallet;
    address public withdrawalWallet;
    uint8 public mintFeePct;
    uint8 MAX_FEE_PCT = 10;

    uint256 public minCommitmentAge;
    uint256 public maxCommitmentAge;
    mapping(bytes32 => uint256) public commitments;

    constructor(
        NameWrapperDelegate _nameWrapperDelegate,
        address _feeWallet,
        address _nameRegistry,
        address _whitelistRegistry,
        address _reservedRegistry,
        address _registryCalls,
        address _validation,
        address _paymentCall,
        address _wethAddress
    ) {
        nameWrapperDelegate = _nameWrapperDelegate;
        feeWallet = _feeWallet;
        withdrawalWallet = _feeWallet;
        mintFeePct = 5;
        registries.push(_nameRegistry);
        registries.push(_whitelistRegistry);
        registries.push(_reservedRegistry);
        registryCalls = _registryCalls;
        validationCalls.push(_validation);
        paymentCall = _paymentCall;
        minCommitmentAge = 30;
        maxCommitmentAge = 86400;
        wethAddress = _wethAddress;
    }

    function commit(bytes32 commitment) external {
        require(
            commitments[commitment] + maxCommitmentAge < block.timestamp,
            "Commitment already exists"
        );
        commitments[commitment] = block.timestamp;
    }

    /**
     * @dev Mints the subname, while validating the registration data and processing the payment.
     * @param label Subname label to register
     * @param parentNode .eth domain node
     * @param addresses Address list - [0]: subnameOwner, [1]: resolver
     * @param extraData Any additional data needed during minting
     */
    function mintSubdomain(
        string calldata label,
        bytes32 parentNode,
        address[] calldata addresses,
        bytes calldata extraData,
        bytes32 secret
    ) external payable {
        _consumeCommitment(
            keccak256(
                abi.encode(label, parentNode, addresses, extraData, secret)
            )
        );

        // 1. update and get the data from the registries
        bytes memory response = _delegateCall(
            registryCalls,
            abi.encodeWithSignature(
                "callRegistries(bytes,address[])",
                abi.encode(parentNode, label, extraData, addresses),
                registries
            ),
            "Registry call error"
        );

        (
            uint256 price,
            address paymentRecevier,
            bytes memory registryData
        ) = abi.decode(response, (uint256, address, bytes));

        // 2. validate the data
        _validate(registryData, extraData, parentNode, label, addresses);

        // 3. mint subname (set the subdomain record) for the new owner
        _mintSubname(parentNode, label, addresses[0], addresses[1]);

        // 4. complete the payment
        _delegateCall(
            paymentCall,
            abi.encodeWithSignature(
                "transfer(bytes)",
                abi.encode(
                    wethAddress,
                    paymentRecevier,
                    feeWallet,
                    price,
                    mintFeePct,
                    registryData,
                    extraData
                )
            ),
            "Payment error"
        );

        emit SubdomainMinted(
            label,
            parentNode,
            price,
            addresses[0],
            paymentRecevier,
            msg.sender
        );
    }

    function _consumeCommitment(bytes32 commitment) internal {
        require(
            commitments[commitment] + minCommitmentAge <= block.timestamp,
            "Commitment not valid yet"
        );

        require(
            commitments[commitment] + maxCommitmentAge >= block.timestamp,
            "Commitment has expired"
        );

        delete (commitments[commitment]);
    }

    function _validate(
        bytes memory registryData,
        bytes memory extraData,
        bytes32 parentNode,
        string calldata label,
        address[] calldata addresses
    ) private {
        bytes memory listingData = abi.encode(
            registryData,
            extraData,
            parentNode,
            label,
            addresses
        );
        for (uint256 i = 0; i < validationCalls.length; i++) {
            _delegateCall(
                validationCalls[i],
                abi.encodeWithSignature("validate(bytes)", listingData),
                "Validation error"
            );
        }
    }

    function _delegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) private returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        if (success) {
            return returndata;
        } else {
            if (returndata.length > 0) {
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }

    function _mintSubname(
        bytes32 parentNode,
        string calldata label,
        address subnameOwner,
        address resolver
    ) private {
        nameWrapperDelegate.setSubnodeRecord(
            parentNode,
            label,
            subnameOwner,
            resolver,
            type(uint64).max,
            CAN_EXTEND_EXPIRY | PARENT_CANNOT_CONTROL,
            type(uint64).max
        );
    }

    function setMintFeePct(uint8 pct) external onlyController {
        require(pct <= MAX_FEE_PCT, "Fee percentage too high");
        mintFeePct = pct;
    }

    function setFeeWallet(address wallet) external onlyController {
        feeWallet = wallet;
    }

    function addRegistry(address registry) external onlyController {
        registries.push(registry);
    }

    function setRegistries(
        address[] calldata _registries
    ) external onlyController {
        registries = _registries;
    }

    function setRegistryCalls(address call) external onlyController {
        registryCalls = call;
    }

    function addValidationCall(address call) external onlyController {
        validationCalls.push(call);
    }

    function setValidationCalls(
        address[] calldata _validationCalls
    ) external onlyController {
        validationCalls = _validationCalls;
    }

    function setPaymentCall(address call) external onlyController {
        paymentCall = call;
    }

    function setNameWrapperDelegate(
        NameWrapperDelegate _nameWrapperDelegate
    ) external onlyController {
        nameWrapperDelegate = _nameWrapperDelegate;
    }

    function withdraw() external onlyController {
        payable(withdrawalWallet).transfer(address(this).balance);
    }

    function setWithdrawalWallet(address wallet) external onlyController {
        withdrawalWallet = wallet;
    }

    function setMinCommitmentAge(uint256 minAge) external onlyController {
        minCommitmentAge = minAge;
    }

    function setMaxCommitmentAge(uint256 maxAge) external onlyController {
        maxCommitmentAge = maxAge;
    }

    function setWethAddress(address _wethAddress) external onlyController {
        wethAddress = _wethAddress;
    }
}

// File: contracts/ens/INameWrapper.sol



//SPDX-License-Identifier: MIT
pragma solidity ~0.8.20;

uint32 constant CANNOT_UNWRAP = 1;
uint32 constant CANNOT_BURN_FUSES = 2;
uint32 constant CANNOT_TRANSFER = 4;
uint32 constant CANNOT_SET_RESOLVER = 8;
uint32 constant CANNOT_SET_TTL = 16;
uint32 constant CANNOT_CREATE_SUBDOMAIN = 32;
uint32 constant CANNOT_APPROVE = 64;
//uint16 reserved for parent controlled fuses from bit 17 to bit 32
uint32 constant PARENT_CANNOT_CONTROL = 1 << 16;
uint32 constant IS_DOT_ETH = 1 << 17;
uint32 constant CAN_EXTEND_EXPIRY = 1 << 18;
uint32 constant CAN_DO_EVERYTHING = 0;
uint32 constant PARENT_CONTROLLED_FUSES = 0xFFFF0000;
// all fuses apart from IS_DOT_ETH
uint32 constant USER_SETTABLE_FUSES = 0xFFFDFFFF;

interface INameWrapper {
    function ownerOf(uint256 id) external view returns (address owner);

    function isApprovedForAll(
        address account,
        address operator
    ) external view returns (bool);

    function setApprovalForAll(address operator, bool approved) external;

    function setSubnodeRecord(
        bytes32 node,
        string calldata label,
        address owner,
        address resolver,
        uint64 ttl,
        uint32 fuses,
        uint64 expiry
    ) external returns (bytes32);

    function getData(
        uint256 tokenId
    ) external view returns (address owner, uint32 fuses, uint64 expiry);

    function setFuses(
        bytes32 node,
        uint16 ownerControlledFuses
    ) external returns (uint32 newFuses);

    function wrapETH2LD(
        string calldata label,
        address wrappedOwner,
        uint16 ownerControlledFuses,
        address resolver
    ) external;

    function canModifyName(
        bytes32 node,
        address addr
    ) external view returns (bool);
}

// File: contracts/ens/NameWrapperDelegate.sol



//SPDX-License-Identifier: MIT
pragma solidity ~0.8.20;

import "../controller/Controllable.sol";
import "../ens/INameWrapper.sol";
import "../libs/Encoder.sol";

contract NameWrapperDelegate is Controllable {
    INameWrapper nameWrapper;

    constructor(INameWrapper _nameWrapper) {
        nameWrapper = _nameWrapper;
    }

    function setSubnodeRecord(
        bytes32 parentNode,
        string calldata label,
        address owner,
        address resolver,
        uint64 ttl,
        uint32 fuses,
        uint64 expiry
    ) external onlyController returns (bytes32 subnameNode) {
        // can only be called when minting a new subname
        require(
            nameWrapper.ownerOf(uint256(_getSubnameNode(parentNode, label))) ==
                address(0),
            "Subdomain already has an owner"
        );

        subnameNode = nameWrapper.setSubnodeRecord(
            parentNode,
            label,
            owner,
            resolver,
            ttl,
            fuses,
            expiry
        );
    }

    function setFuses(bytes32 node, uint16 fuse) external onlyController {
        nameWrapper.setFuses(node, fuse);
    }

    function _getSubnameNode(
        bytes32 parentNode,
        string calldata label
    ) private pure returns (bytes32) {
        bytes32 labelHash = keccak256(bytes(label));
        return Encoder.encodeNode(parentNode, labelHash);
    }
}

// File: contracts/libs/Encoder.sol



//SPDX-License-Identifier: MIT
pragma solidity ~0.8.20;

library Encoder {
    function encodeNode(
        bytes32 node,
        bytes32 labelHash
    ) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(node, labelHash));
    }
}