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

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

/// @title AereModularAccountFactory - CREATE2 factory for AereModularAccount
/// @notice Deterministic: getAddress(rootOwner, salt) equals the deployed address.
///         The root owner is mixed into the CREATE2 salt so nobody can front-run
///         a counterfactual address with a different owner. Follows the
///         AerePasskeyAccountFactoryV2 conventions (idempotent createAccount,
///         immutable default EntryPoint).
contract AereModularAccountFactory {
    bytes32 public constant ACCOUNT_INIT_CODE_HASH = keccak256(type(AereModularAccount).creationCode);

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

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

    error ZeroEntryPoint();
    error AddressMismatch();

    constructor(address _defaultEntryPoint) {
        if (_defaultEntryPoint == address(0)) revert ZeroEntryPoint();
        defaultEntryPoint = _defaultEntryPoint;
    }

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

    /// @notice Idempotent: returns the existing account if already deployed.
    function createAccount(address rootOwner, uint256 salt)
        external
        returns (AereModularAccount account)
    {
        address predicted = getAddress(rootOwner, salt);
        if (predicted.code.length > 0) {
            return AereModularAccount(payable(predicted));
        }
        account = new AereModularAccount{salt: _saltMix(rootOwner, salt)}();
        account.initialize(defaultEntryPoint, rootOwner);
        if (address(account) != predicted) revert AddressMismatch();
        emit AccountCreated(address(account), rootOwner, defaultEntryPoint, salt);
    }

    function _saltMix(address rootOwner, uint256 salt) internal pure returns (bytes32) {
        return keccak256(abi.encode(rootOwner, salt));
    }
}
