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

/// @title MultiOwnable — variant ownership with both EOA and WebAuthn passkey owners
/// @notice Adapted from Coinbase Smart Wallet's MultiOwnable.sol (MIT).
///         Owners are stored as raw bytes: 32 bytes (left-padded EOA address) OR
///         64 bytes (P-256 uncompressed point: x||y). Each owner has an index.
///         Any owner can add or remove other owners. Loss of one owner ≠ loss of account
///         as long as ≥1 other owner remains.
///
/// @dev    Source: https://github.com/coinbase/smart-wallet/blob/main/src/MultiOwnable.sol
contract MultiOwnable {
    /// @notice Tracks the next owner index to assign. Monotonic — never reused.
    uint256 public nextOwnerIndex;

    /// @notice Number of currently-registered owners (decreases on remove).
    uint256 public ownerCount;

    /// @notice Indexed owner storage.
    mapping(uint256 => bytes) internal _ownerAtIndex;

    /// @notice Reverse lookup: bytes-encoded owner → true if registered.
    mapping(bytes => bool) internal _isOwner;

    event AddOwner(uint256 indexed index, bytes owner);
    event RemoveOwner(uint256 indexed index, bytes owner);

    error Unauthorized();
    error AlreadyOwner(bytes owner);
    error NoOwnerAtIndex(uint256 index);
    error InvalidOwnerBytesLength(uint256 length);
    error InvalidEthereumAddressOwner(bytes owner);
    error ZeroAddressOwner();
    error LastOwner();

    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /// @notice Self-call only. Used so external callers can never bypass auth — every
    ///         owner-management op must come through an authenticated execute() that
    ///         loops back through msg.sender == address(this).
    function _checkOwner() internal view virtual {
        if (msg.sender != address(this)) revert Unauthorized();
    }

    /// @notice Add an EOA owner by address (20 bytes left-padded to 32).
    function addOwnerAddress(address owner) public virtual onlyOwner {
        _addOwner(abi.encode(owner));
    }

    /// @notice Add a passkey owner by uncompressed P-256 public key (64 bytes: x || y).
    function addOwnerPublicKey(bytes32 x, bytes32 y) public virtual onlyOwner {
        _addOwner(abi.encode(x, y));
    }

    function _addOwner(bytes memory owner) internal {
        if (_isOwner[owner]) revert AlreadyOwner(owner);
        if (owner.length != 32 && owner.length != 64) revert InvalidOwnerBytesLength(owner.length);
        if (owner.length == 32) {
            // EOA owner — top 12 bytes must be zero (uint160 left-padded to bytes32).
            uint256 word;
            // solhint-disable-next-line no-inline-assembly
            assembly { word := mload(add(owner, 0x20)) }
            if (word >> 160 != 0) revert InvalidEthereumAddressOwner(owner);
            // Ban address(0) as an owner: paired with an ecrecover-failure returning
            // address(0), a zero owner would let a malformed signature authorize.
            if (word == 0) revert ZeroAddressOwner();
        }
        uint256 idx = nextOwnerIndex++;
        _ownerAtIndex[idx] = owner;
        _isOwner[owner] = true;
        ownerCount += 1;
        emit AddOwner(idx, owner);
    }

    /// @notice Remove the owner at a specific index. Reverts if it would zero the set.
    function removeOwnerAtIndex(uint256 index) public virtual onlyOwner {
        bytes memory owner = _ownerAtIndex[index];
        if (owner.length == 0) revert NoOwnerAtIndex(index);
        if (ownerCount == 1) revert LastOwner();
        delete _isOwner[owner];
        delete _ownerAtIndex[index];
        ownerCount -= 1;
        emit RemoveOwner(index, owner);
    }

    function isOwnerAddress(address account) public view virtual returns (bool) {
        return _isOwner[abi.encode(account)];
    }

    function isOwnerPublicKey(bytes32 x, bytes32 y) public view virtual returns (bool) {
        return _isOwner[abi.encode(x, y)];
    }

    function isOwnerBytes(bytes memory account) public view virtual returns (bool) {
        return _isOwner[account];
    }

    function ownerAtIndex(uint256 index) public view virtual returns (bytes memory) {
        return _ownerAtIndex[index];
    }

    /// @notice Internal initializer — called by inheriting contract during init.
    function _initializeOwners(bytes[] memory owners) internal {
        for (uint256 i = 0; i < owners.length; i++) {
            _addOwner(owners[i]);
        }
    }
}
