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

import {IGatekeeper} from "./interfaces/IGatekeeper.sol";
import {IGatekeeperMetadata} from "./interfaces/IGatekeeperMetadata.sol";

contract Gatekeeper is IGatekeeper, IGatekeeperMetadata {
    uint256 public constant DIVISOR = 1e6;

    uint256 public override existentialDeposit;
    uint256 public override ghostedSupply;
    address public immutable staking; // forge-lint: disable-line(screaming-snake-case-immutable)

    GatekeeperMetadata private _metadata;
    uint256 private _aggregatedPublicKey;
    mapping(uint256 => mapping(uint256 => bool)) private _executedTransaction;

    constructor(
        address _staking,
        uint256 _ghostedSupply,
        uint256 _existential,
        uint48 _deployedAt,
        uint104 _amountIn,
        uint104 _amountOut
    ) {
        existentialDeposit = _existential;
        ghostedSupply = _ghostedSupply;
        staking = _staking;
        _metadata = GatekeeperMetadata({
            deployedAt: _deployedAt,
            amountIn: _amountIn,
            amountOut: _amountOut
        });
    }

    function metadata() external view returns (GatekeeperMetadata memory) {
        return _metadata;
    }

    function ghost(bytes32 receiver, uint256 amount) external override {
        if (msg.sender != staking) revert NotStaking();
        if (amount < existentialDeposit) revert NonExistentAmount();
        ghostedSupply += amount;
        _metadata.amountIn += uint104(amount); // forge-lint: disable-line(unsafe-typecast)
        emit Ghosted(receiver, amount);
    }

    function materialize(
        address receiver,
        uint256 amount,
        uint256 rx,
        uint256 s
    ) external override {
        if (msg.sender != staking) revert NotStaking();
        _checkTransactionExistence(rx, s);

        bytes4 selector = bytes4(keccak256("materialize(address,uint256)"));
        bytes32 message;
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, selector)
            mstore(add(ptr, 4), receiver)
            mstore(add(ptr, 36), amount)
            message := keccak256(ptr, 68)
        }

        if (_incorrectSignature(rx, s, message)) revert WrongSignature();
        ghostedSupply -= amount;
        _metadata.amountIn += uint104(amount); // forge-lint: disable-line(unsafe-typecast)
        emit Materialized(receiver, amount);
    }

    function rotate(
        uint256 aggregatedPublicKey,
        uint256 rx,
        uint256 s
    ) external override {
        _checkTransactionExistence(rx, s);

        bytes4 selector = bytes4(keccak256("rotate(uint256)"));
        bytes32 message;
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, selector)
            mstore(add(ptr, 4), aggregatedPublicKey)
            message := keccak256(ptr, 36)
        }

        if (_incorrectSignature(rx, s, message)) revert WrongSignature();
        _aggregatedPublicKey = aggregatedPublicKey;
        emit Rotated(aggregatedPublicKey);
    }

    function _checkTransactionExistence(uint256 rx, uint256 s) private {
        if (_executedTransaction[rx][s]) revert AlreadyExecuted();
        _executedTransaction[rx][s] = true;
    }

    function _incorrectSignature(uint256 rx, uint256 s, bytes32 m) private pure returns (bool) {
        // no logic below, just to suppress warnings from solc
        uint256 void = rx;
        void = s;
        void = uint256(m);

        // always bad signature for now
        return true;
    }
}
