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

import {AerePQCAccount} from "./AerePQCAccount.sol";

/**
 * @title AerePQCAccountFactory, CREATE2 factory for post-quantum smart accounts (roadmap #270)
 * @notice Deterministically deploys AerePQCAccount instances keyed on a NIST
 *         Falcon-512 public key (897 bytes) plus a salt, mirroring
 *         AerePasskeyAccountFactoryV2. The account address is a pure function of
 *         (falconPubKey, salt): the same Falcon key with the same salt always
 *         maps to the same address, so an account can be funded at its
 *         counterfactual address before it is deployed, and re-deploying is a
 *         no-op that returns the existing account.
 *
 * @dev    All accounts from one factory share the factory's EntryPoint and
 *         Falcon-512 verifier (both immutable, set at factory deployment), so the
 *         account creationCode carries no constructor args and the CREATE2 init
 *         code hash is a constant.
 */
contract AerePQCAccountFactory {
    /// @notice keccak256 of the AerePQCAccount creation bytecode (no constructor args).
    bytes32 public constant ACCOUNT_INIT_CODE_HASH = keccak256(type(AerePQCAccount).creationCode);

    /// @notice NIST Falcon-512 public key length + header (0x00 | logn=9).
    uint256 internal constant FALCON512_PK_LEN = 897;
    uint8 internal constant FALCON512_PK_HEADER = 0x09;

    /// @notice The EntryPoint every account created by this factory trusts.
    address public immutable defaultEntryPoint;

    /// @notice The live AereFalcon512Verifier every account delegates verification to.
    address public immutable falconVerifier;

    event AccountCreated(address indexed account, address indexed entryPoint, bytes falconPubKey, uint256 salt);

    error InvalidEntryPoint();
    error InvalidFalconVerifier();
    error InvalidFalconPubKey();
    error AddressMismatch();

    constructor(address _defaultEntryPoint, address _falconVerifier) {
        if (_defaultEntryPoint == address(0)) revert InvalidEntryPoint();
        if (_falconVerifier == address(0)) revert InvalidFalconVerifier();
        defaultEntryPoint = _defaultEntryPoint;
        falconVerifier = _falconVerifier;
    }

    /// @notice Deterministic account address for (falconPubKey, salt).
    function predictAddress(bytes calldata falconPubKey, uint256 salt) public view returns (address) {
        bytes32 saltMix = _saltMix(falconPubKey, salt);
        return address(uint160(uint256(keccak256(abi.encodePacked(
            bytes1(0xff), address(this), saltMix, ACCOUNT_INIT_CODE_HASH
        )))));
    }

    /// @notice Idempotent: deploys + initialises the account for `falconPubKey`, or
    ///         returns the already-deployed account at the same address.
    function createAccount(bytes calldata falconPubKey, uint256 salt)
        external returns (AerePQCAccount account)
    {
        if (falconPubKey.length != FALCON512_PK_LEN || uint8(falconPubKey[0]) != FALCON512_PK_HEADER) {
            revert InvalidFalconPubKey();
        }
        address predicted = predictAddress(falconPubKey, salt);
        if (predicted.code.length > 0) {
            return AerePQCAccount(payable(predicted));
        }
        bytes32 saltMix = _saltMix(falconPubKey, salt);
        account = new AerePQCAccount{salt: saltMix}();
        account.initialize(defaultEntryPoint, falconVerifier, falconPubKey);
        if (address(account) != predicted) revert AddressMismatch();
        emit AccountCreated(address(account), defaultEntryPoint, falconPubKey, salt);
    }

    function _saltMix(bytes calldata falconPubKey, uint256 salt) internal pure returns (bytes32) {
        return keccak256(abi.encode(falconPubKey, salt));
    }
}
