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

import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/interfaces/IERC1271.sol";
import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";

/// @dev ERC-6551 account interface (ERC-165 id 0x6faff5f1).
interface IERC6551Account {
    receive() external payable;

    function token()
        external
        view
        returns (uint256 chainId, address tokenContract, uint256 tokenId);

    function state() external view returns (uint256);

    function isValidSigner(address signer, bytes calldata context)
        external
        view
        returns (bytes4 magicValue);
}

/// @dev ERC-6551 executable interface (ERC-165 id 0x51945447).
interface IERC6551Executable {
    function execute(address to, uint256 value, bytes calldata data, uint8 operation)
        external
        payable
        returns (bytes memory);
}

/**
 * @title AereTokenBoundAccount — ERC-6551 token-bound smart account for AERE.
 *
 * @notice A wallet OWNED BY AN NFT. Deployed as a deterministic ERC-1167 proxy
 *         by the canonical ERC6551Registry and bound at creation to a specific
 *         (chainId, tokenContract, tokenId). Whoever currently holds that NFT
 *         controls this account: they can execute arbitrary calls, and the
 *         account produces EIP-1271 signatures on their behalf. The account can
 *         hold native AERE and any tokens/NFTs like any other address.
 *
 *         This is an ACCOUNT PRIMITIVE, not a fungible/currency/governance token.
 *         It mints nothing, has no supply, and issues no transferable claim. Its
 *         only authority source is `ownerOf(tokenId)` on the bound ERC-721.
 *
 *         EXECUTION + SIGNING (reuses AERE's 4337 / EIP-1271 pattern from
 *         AerePasskeyAccountV2): the same "owner authenticates, then call the
 *         target" shape, and the same EIP-1271 `isValidSignature` shape, but the
 *         owner set is derived from NFT ownership rather than a stored key set.
 *
 *         Two authorization paths:
 *           1. execute(...)              — msg.sender must be the current NFT holder.
 *           2. validateUserOp / executeFromEntryPoint — ERC-4337 path through the
 *              deployed AereEntryPointV2 (0x8D6f…aF770); the owner signs the
 *              userOpHash and the EntryPoint relays. Same 4337 shape as the
 *              passkey account, letting a bundler/paymaster sponsor the NFT
 *              wallet's gas.
 *
 *         Only CALL (operation 0) is supported; DELEGATECALL/CREATE are rejected
 *         so the bound account can never be hijacked into changing its own logic.
 */
contract AereTokenBoundAccount is
    IERC165,
    IERC1271,
    IERC6551Account,
    IERC6551Executable
{
    /// @notice Monotonic counter bumped on every successful execute — ERC-6551 `state`.
    uint256 public state;

    /// @notice Deployed AERE ERC-4337 EntryPoint (AereEntryPointV2). Fixed constant:
    ///         ERC-6551 proxies share one implementation and take no constructor args.
    address public constant ENTRY_POINT = 0x8D6f40598d552fF0Cb358b6012cF4227B86aF770;

    /// @notice EIP-1271 magic value on success.
    bytes4 internal constant EIP1271_MAGIC = 0x1626ba7e;

    /// @notice ERC-4337 v0.7 PackedUserOperation — identical shape to AereEntryPointV2.
    struct PackedUserOperation {
        address sender;
        uint256 nonce;
        bytes initCode;
        bytes callData;
        bytes32 accountGasLimits;
        uint256 preVerificationGas;
        bytes32 gasFees;
        bytes paymasterAndData;
        bytes signature;
    }

    event Executed(uint256 indexed state, address indexed to, uint256 value, bytes data);

    error InvalidSigner();
    error UnsupportedOperation(uint8 operation);
    error NotFromEntryPoint();

    receive() external payable {}

    // ─── ERC-6551 execution (direct owner path) ───────────────────────────────

    /// @notice Execute a call authorized by the current NFT holder.
    /// @param operation must be 0 (CALL); anything else reverts.
    function execute(address to, uint256 value, bytes calldata data, uint8 operation)
        external
        payable
        override
        returns (bytes memory result)
    {
        if (!_isValidSigner(msg.sender)) revert InvalidSigner();
        if (operation != 0) revert UnsupportedOperation(operation);

        ++state;

        bool success;
        (success, result) = to.call{value: value}(data);
        if (!success) {
            // bubble up the revert reason
            assembly {
                revert(add(result, 32), mload(result))
            }
        }
        emit Executed(state, to, value, data);
    }

    // ─── ERC-4337 path (through AereEntryPointV2) ─────────────────────────────

    /// @notice ERC-4337 validation. Returns 0 on success, 1 on bad signature
    ///         (per spec — does not revert on signature failure).
    function validateUserOp(
        PackedUserOperation calldata userOp,
        bytes32 userOpHash,
        uint256 missingAccountFunds
    ) external returns (uint256 validationData) {
        if (msg.sender != ENTRY_POINT) revert NotFromEntryPoint();

        // Owner (the NFT holder) must have signed the userOpHash. SignatureChecker
        // supports both EOA holders and smart-contract (EIP-1271) holders.
        bool ok = SignatureChecker.isValidSignatureNow(owner(), userOpHash, userOp.signature);
        if (!ok) return 1; // SIG_VALIDATION_FAILED

        if (missingAccountFunds > 0) {
            (bool sent, ) = payable(msg.sender).call{value: missingAccountFunds}("");
            (sent); // EntryPoint reverts if underfunded
        }
        return 0;
    }

    /// @notice Post-validation execution dispatched by the EntryPoint.
    function executeFromEntryPoint(address to, uint256 value, bytes calldata data)
        external
        returns (bytes memory result)
    {
        if (msg.sender != ENTRY_POINT) revert NotFromEntryPoint();
        ++state;
        bool success;
        (success, result) = to.call{value: value}(data);
        if (!success) {
            assembly {
                revert(add(result, 32), mload(result))
            }
        }
        emit Executed(state, to, value, data);
    }

    // ─── ERC-6551 signer / EIP-1271 ───────────────────────────────────────────

    function isValidSigner(address signer, bytes calldata)
        external
        view
        override
        returns (bytes4)
    {
        if (_isValidSigner(signer)) return IERC6551Account.isValidSigner.selector;
        return bytes4(0);
    }

    /// @notice EIP-1271: an off-chain signature is valid iff it is a valid
    ///         signature by the current NFT holder over `hash`.
    function isValidSignature(bytes32 hash, bytes memory signature)
        external
        view
        override
        returns (bytes4 magicValue)
    {
        return SignatureChecker.isValidSignatureNow(owner(), hash, signature)
            ? EIP1271_MAGIC
            : bytes4(0);
    }

    // ─── ERC-6551 binding / ownership ─────────────────────────────────────────

    /// @notice The (chainId, tokenContract, tokenId) this account is bound to,
    ///         read from the ERC-1167 footer appended by the registry.
    function token()
        public
        view
        override
        returns (uint256 chainId, address tokenContract, uint256 tokenId)
    {
        bytes memory footer = new bytes(0x60);
        assembly {
            // proxy runtime is 45 bytes; the 3 immutable words start at 0x4d.
            extcodecopy(address(), add(footer, 0x20), 0x4d, 0x60)
        }
        return abi.decode(footer, (uint256, address, uint256));
    }

    /// @notice The current controller = holder of the bound NFT.
    function owner() public view returns (address) {
        (uint256 chainId, address tokenContract, uint256 tokenId) = token();
        if (chainId != block.chainid) return address(0);
        return IERC721(tokenContract).ownerOf(tokenId);
    }

    function _isValidSigner(address signer) internal view returns (bool) {
        return signer != address(0) && signer == owner();
    }

    // ─── ERC-165 ──────────────────────────────────────────────────────────────

    function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
        return interfaceId == type(IERC165).interfaceId
            || interfaceId == type(IERC6551Account).interfaceId
            || interfaceId == type(IERC6551Executable).interfaceId
            || interfaceId == type(IERC1271).interfaceId;
    }
}
