# Copyright 2021-2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
from __future__ import annotations

import dataclasses
import enum
import functools
import struct
from collections.abc import Iterable
from typing import (
    Any,
    ClassVar,
    Literal,
    cast,
    overload,
)

from typing_extensions import Self

from bumble import utils
from bumble.company_ids import COMPANY_IDENTIFIERS

# -----------------------------------------------------------------------------
# Constants
# -----------------------------------------------------------------------------
# fmt: off

class PhysicalTransport(enum.IntEnum):
    BR_EDR = 0
    LE     = 1

BT_BR_EDR_TRANSPORT = PhysicalTransport.BR_EDR
BT_LE_TRANSPORT     = PhysicalTransport.LE


# fmt: on


# -----------------------------------------------------------------------------
# Utils
# -----------------------------------------------------------------------------
def bit_flags_to_strings(bits, bit_flag_names):
    names = []
    index = 0
    while bits != 0:
        if bits & 1:
            name = bit_flag_names[index] if index < len(bit_flag_names) else f'#{index}'
            names.append(name)
        bits >>= 1
        index += 1

    return names


def name_or_number(dictionary: dict[int, str], number: int, width: int = 2) -> str:
    name = dictionary.get(number)
    if name is not None:
        return name
    return f'[0x{number:0{width}X}]'


def padded_bytes(buffer, size):
    padding_size = max(size - len(buffer), 0)
    return buffer + bytes(padding_size)


def get_dict_key_by_value(dictionary, value):
    for key, val in dictionary.items():
        if val == value:
            return key
    return None


# -----------------------------------------------------------------------------
# Exceptions
# -----------------------------------------------------------------------------


class BaseBumbleError(Exception):
    """Base Error raised by Bumble."""


class BaseError(BaseBumbleError):
    """Base class for errors with an error code, error name and namespace"""

    def __init__(
        self,
        error_code: int | None,
        error_namespace: str = '',
        error_name: str = '',
        details: str = '',
    ):
        super().__init__()
        self.error_code = error_code
        self.error_namespace = error_namespace
        self.error_name = error_name
        self.details = details

    def __str__(self):
        if self.error_namespace:
            namespace = f'{self.error_namespace}/'
        else:
            namespace = ''
        have_name = self.error_name != ''
        have_code = self.error_code is not None
        if have_name and have_code:
            error_text = f'{self.error_name} [0x{self.error_code:X}]'
        elif have_name and not have_code:
            error_text = self.error_name
        elif not have_name and have_code:
            error_text = f'0x{self.error_code:X}'
        else:
            error_text = '<unspecified>'

        return f'{type(self).__name__}({namespace}{error_text})'


class ProtocolError(BaseError):
    """Protocol Error"""


class TimeoutError(BaseBumbleError):  # pylint: disable=redefined-builtin
    """Timeout Error"""


class CommandTimeoutError(BaseBumbleError):
    """Command Timeout Error"""


class InvalidStateError(BaseBumbleError):
    """Invalid State Error"""


class InvalidArgumentError(BaseBumbleError, ValueError):
    """Invalid Argument Error"""


class InvalidPacketError(BaseBumbleError, ValueError):
    """Invalid Packet Error"""


class InvalidOperationError(BaseBumbleError, RuntimeError):
    """Invalid Operation Error"""


class NotSupportedError(BaseBumbleError, RuntimeError):
    """Not Supported"""


class OutOfResourcesError(BaseBumbleError, RuntimeError):
    """Out of Resources Error"""


class UnreachableError(BaseBumbleError):
    """The code path raising this error should be unreachable."""


class ConnectionError(BaseError):  # pylint: disable=redefined-builtin
    """Connection Error"""

    FAILURE = 0x01
    CONNECTION_REFUSED = 0x02

    def __init__(
        self,
        error_code,
        transport,
        peer_address,
        error_namespace='',
        error_name='',
        details='',
    ):
        super().__init__(error_code, error_namespace, error_name, details)
        self.transport = transport
        self.peer_address = peer_address


class ConnectionParameterUpdateError(BaseError):
    """Connection Parameter Update Error"""


# -----------------------------------------------------------------------------
# UUID
#
# NOTE: the internal byte representation is in little-endian byte order
#
# Base UUID: 00000000-0000-1000-8000- 00805F9B34FB
# -----------------------------------------------------------------------------
class UUID:
    '''
    See Bluetooth spec Vol 3, Part B - 2.5.1 UUID

    Note that this class expects and works in little-endian byte-order throughout.
    The exception is when interacting with strings, which are in big-endian byte-order.
    '''

    BASE_UUID = bytes.fromhex('00001000800000805F9B34FB')[::-1]  # little-endian
    UUIDS: list[UUID] = []  # Registry of all instances created

    uuid_bytes: bytes
    name: str | None

    def __init__(self, uuid_str_or_int: str | int, name: str | None = None) -> None:
        if isinstance(uuid_str_or_int, int):
            self.uuid_bytes = struct.pack('<H', uuid_str_or_int)
        else:
            if len(uuid_str_or_int) == 36:
                if (
                    uuid_str_or_int[8] != '-'
                    or uuid_str_or_int[13] != '-'
                    or uuid_str_or_int[18] != '-'
                    or uuid_str_or_int[23] != '-'
                ):
                    raise InvalidArgumentError('invalid UUID format')
                uuid_str = uuid_str_or_int.replace('-', '')
            else:
                uuid_str = uuid_str_or_int
            if len(uuid_str) != 32 and len(uuid_str) != 8 and len(uuid_str) != 4:
                raise InvalidArgumentError(f"invalid UUID format: {uuid_str}")
            self.uuid_bytes = bytes(reversed(bytes.fromhex(uuid_str)))
        self.name = name

    def register(self) -> UUID:
        # Register this object in the class registry, and update the entry's name if
        # it wasn't set already
        for uuid in self.UUIDS:
            if self == uuid:
                if uuid.name is None:
                    uuid.name = self.name
                return uuid

        self.UUIDS.append(self)
        return self

    @classmethod
    def from_bytes(cls, uuid_bytes: bytes, name: str | None = None) -> UUID:
        if len(uuid_bytes) in (2, 4, 16):
            self = cls.__new__(cls)
            self.uuid_bytes = uuid_bytes
            self.name = name

            return self.register()

        raise InvalidArgumentError('only 2, 4 and 16 bytes are allowed')

    @classmethod
    def from_16_bits(cls, uuid_16: int, name: str | None = None) -> UUID:
        return cls.from_bytes(struct.pack('<H', uuid_16), name)

    @classmethod
    def from_32_bits(cls, uuid_32: int, name: str | None = None) -> UUID:
        return cls.from_bytes(struct.pack('<I', uuid_32), name)

    @classmethod
    def parse_uuid(cls, uuid_as_bytes: bytes, offset: int) -> tuple[int, UUID]:
        return len(uuid_as_bytes), cls.from_bytes(uuid_as_bytes[offset:])

    @classmethod
    def parse_uuid_2(cls, uuid_as_bytes: bytes, offset: int) -> tuple[int, UUID]:
        return offset + 2, cls.from_bytes(uuid_as_bytes[offset : offset + 2])

    @functools.cached_property
    def uuid_128_bytes(self) -> bytes:
        match len(self.uuid_bytes):
            case 2:
                return self.BASE_UUID + self.uuid_bytes + bytes([0, 0])
            case 4:
                return self.BASE_UUID + self.uuid_bytes
            case 16:
                return self.uuid_bytes
            case _:
                assert False, "unreachable"

    def to_bytes(self, force_128: bool = False) -> bytes:
        '''
        Serialize UUID in little-endian byte-order
        '''
        if not force_128:
            return self.uuid_bytes

        return self.uuid_128_bytes

    def to_pdu_bytes(self) -> bytes:
        '''
        Convert to bytes for use in an ATT PDU.
        According to Vol 3, Part F - 3.2.1 Attribute Type:
        "All 32-bit Attribute UUIDs shall be converted to 128-bit UUIDs when the
         Attribute UUID is contained in an ATT PDU."
        '''
        return self.to_bytes(force_128=(len(self.uuid_bytes) == 4))

    def to_hex_str(self, separator: str = '') -> str:
        if len(self.uuid_bytes) == 2 or len(self.uuid_bytes) == 4:
            return bytes(reversed(self.uuid_bytes)).hex().upper()

        return separator.join(
            [
                bytes(reversed(self.uuid_bytes[12:16])).hex(),
                bytes(reversed(self.uuid_bytes[10:12])).hex(),
                bytes(reversed(self.uuid_bytes[8:10])).hex(),
                bytes(reversed(self.uuid_bytes[6:8])).hex(),
                bytes(reversed(self.uuid_bytes[0:6])).hex(),
            ]
        ).upper()

    def __bytes__(self) -> bytes:
        return self.to_bytes()

    def __eq__(self, other: object) -> bool:
        if isinstance(other, UUID):
            return self.uuid_128_bytes == other.uuid_128_bytes

        if isinstance(other, str):
            return UUID(other) == self

        return False

    def __hash__(self) -> int:
        return hash(self.uuid_128_bytes)

    def __str__(self) -> str:
        result = self.to_hex_str(separator='-')
        if len(self.uuid_bytes) == 2:
            result = 'UUID-16:' + result
        elif len(self.uuid_bytes) == 4:
            result = 'UUID-32:' + result
        if self.name is not None:
            result += f' ({self.name})'
        return result

    def __repr__(self) -> str:
        return self.to_hex_str()


# -----------------------------------------------------------------------------
# Common UUID constants
# -----------------------------------------------------------------------------
# fmt: off
# pylint: disable=line-too-long

# Protocol Identifiers
BT_SDP_PROTOCOL_ID                      = UUID.from_16_bits(0x0001, 'SDP')
BT_UDP_PROTOCOL_ID                      = UUID.from_16_bits(0x0002, 'UDP')
BT_RFCOMM_PROTOCOL_ID                   = UUID.from_16_bits(0x0003, 'RFCOMM')
BT_TCP_PROTOCOL_ID                      = UUID.from_16_bits(0x0004, 'TCP')
BT_TCS_BIN_PROTOCOL_ID                  = UUID.from_16_bits(0x0005, 'TCP-BIN')
BT_TCS_AT_PROTOCOL_ID                   = UUID.from_16_bits(0x0006, 'TCS-AT')
BT_ATT_PROTOCOL_ID                      = UUID.from_16_bits(0x0007, 'ATT')
BT_OBEX_PROTOCOL_ID                     = UUID.from_16_bits(0x0008, 'OBEX')
BT_IP_PROTOCOL_ID                       = UUID.from_16_bits(0x0009, 'IP')
BT_FTP_PROTOCOL_ID                      = UUID.from_16_bits(0x000A, 'FTP')
BT_HTTP_PROTOCOL_ID                     = UUID.from_16_bits(0x000C, 'HTTP')
BT_WSP_PROTOCOL_ID                      = UUID.from_16_bits(0x000E, 'WSP')
BT_BNEP_PROTOCOL_ID                     = UUID.from_16_bits(0x000F, 'BNEP')
BT_UPNP_PROTOCOL_ID                     = UUID.from_16_bits(0x0010, 'UPNP')
BT_HIDP_PROTOCOL_ID                     = UUID.from_16_bits(0x0011, 'HIDP')
BT_HARDCOPY_CONTROL_CHANNEL_PROTOCOL_ID = UUID.from_16_bits(0x0012, 'HardcopyControlChannel')
BT_HARDCOPY_DATA_CHANNEL_PROTOCOL_ID    = UUID.from_16_bits(0x0014, 'HardcopyDataChannel')
BT_HARDCOPY_NOTIFICATION_PROTOCOL_ID    = UUID.from_16_bits(0x0016, 'HardcopyNotification')
BT_AVCTP_PROTOCOL_ID                    = UUID.from_16_bits(0x0017, 'AVCTP')
BT_AVDTP_PROTOCOL_ID                    = UUID.from_16_bits(0x0019, 'AVDTP')
BT_CMTP_PROTOCOL_ID                     = UUID.from_16_bits(0x001B, 'CMTP')
BT_MCAP_CONTROL_CHANNEL_PROTOCOL_ID     = UUID.from_16_bits(0x001E, 'MCAPControlChannel')
BT_MCAP_DATA_CHANNEL_PROTOCOL_ID        = UUID.from_16_bits(0x001F, 'MCAPDataChannel')
BT_L2CAP_PROTOCOL_ID                    = UUID.from_16_bits(0x0100, 'L2CAP')

# Service Classes and Profiles
BT_SERVICE_DISCOVERY_SERVER_SERVICE_CLASS_ID_SERVICE = UUID.from_16_bits(0x1000, 'ServiceDiscoveryServerServiceClassID')
BT_BROWSE_GROUP_DESCRIPTOR_SERVICE_CLASS_ID_SERVICE  = UUID.from_16_bits(0x1001, 'BrowseGroupDescriptorServiceClassID')
BT_SERIAL_PORT_SERVICE                               = UUID.from_16_bits(0x1101, 'SerialPort')
BT_LAN_ACCESS_USING_PPP_SERVICE                      = UUID.from_16_bits(0x1102, 'LANAccessUsingPPP')
BT_DIALUP_NETWORKING_SERVICE                         = UUID.from_16_bits(0x1103, 'DialupNetworking')
BT_IR_MCSYNC_SERVICE                                 = UUID.from_16_bits(0x1104, 'IrMCSync')
BT_OBEX_OBJECT_PUSH_SERVICE                          = UUID.from_16_bits(0x1105, 'OBEXObjectPush')
BT_OBEX_FILE_TRANSFER_SERVICE                        = UUID.from_16_bits(0x1106, 'OBEXFileTransfer')
BT_IR_MCSYNC_COMMAND_SERVICE                         = UUID.from_16_bits(0x1107, 'IrMCSyncCommand')
BT_HEADSET_SERVICE                                   = UUID.from_16_bits(0x1108, 'Headset')
BT_CORDLESS_TELEPHONY_SERVICE                        = UUID.from_16_bits(0x1109, 'CordlessTelephony')
BT_AUDIO_SOURCE_SERVICE                              = UUID.from_16_bits(0x110A, 'AudioSource')
BT_AUDIO_SINK_SERVICE                                = UUID.from_16_bits(0x110B, 'AudioSink')
BT_AV_REMOTE_CONTROL_TARGET_SERVICE                  = UUID.from_16_bits(0x110C, 'A/V_RemoteControlTarget')
BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE               = UUID.from_16_bits(0x110D, 'AdvancedAudioDistribution')
BT_AV_REMOTE_CONTROL_SERVICE                         = UUID.from_16_bits(0x110E, 'A/V_RemoteControl')
BT_AV_REMOTE_CONTROL_CONTROLLER_SERVICE              = UUID.from_16_bits(0x110F, 'A/V_RemoteControlController')
BT_INTERCOM_SERVICE                                  = UUID.from_16_bits(0x1110, 'Intercom')
BT_FAX_SERVICE                                       = UUID.from_16_bits(0x1111, 'Fax')
BT_HEADSET_AUDIO_GATEWAY_SERVICE                     = UUID.from_16_bits(0x1112, 'Headset - Audio Gateway')
BT_WAP_SERVICE                                       = UUID.from_16_bits(0x1113, 'WAP')
BT_WAP_CLIENT_SERVICE                                = UUID.from_16_bits(0x1114, 'WAP_CLIENT')
BT_PANU_SERVICE                                      = UUID.from_16_bits(0x1115, 'PANU')
BT_NAP_SERVICE                                       = UUID.from_16_bits(0x1116, 'NAP')
BT_GN_SERVICE                                        = UUID.from_16_bits(0x1117, 'GN')
BT_DIRECT_PRINTING_SERVICE                           = UUID.from_16_bits(0x1118, 'DirectPrinting')
BT_REFERENCE_PRINTING_SERVICE                        = UUID.from_16_bits(0x1119, 'ReferencePrinting')
BT_BASIC_IMAGING_PROFILE_SERVICE                     = UUID.from_16_bits(0x111A, 'Basic Imaging Profile')
BT_IMAGING_RESPONDER_SERVICE                         = UUID.from_16_bits(0x111B, 'ImagingResponder')
BT_IMAGING_AUTOMATIC_ARCHIVE_SERVICE                 = UUID.from_16_bits(0x111C, 'ImagingAutomaticArchive')
BT_IMAGING_REFERENCED_OBJECTS_SERVICE                = UUID.from_16_bits(0x111D, 'ImagingReferencedObjects')
BT_HANDSFREE_SERVICE                                 = UUID.from_16_bits(0x111E, 'Handsfree')
BT_HANDSFREE_AUDIO_GATEWAY_SERVICE                   = UUID.from_16_bits(0x111F, 'HandsfreeAudioGateway')
BT_DIRECT_PRINTING_REFERENCE_OBJECTS_SERVICE         = UUID.from_16_bits(0x1120, 'DirectPrintingReferenceObjectsService')
BT_REFLECTED_UI_SERVICE                              = UUID.from_16_bits(0x1121, 'ReflectedUI')
BT_BASIC_PRINTING_SERVICE                            = UUID.from_16_bits(0x1122, 'BasicPrinting')
BT_PRINTING_STATUS_SERVICE                           = UUID.from_16_bits(0x1123, 'PrintingStatus')
BT_HUMAN_INTERFACE_DEVICE_SERVICE                    = UUID.from_16_bits(0x1124, 'HumanInterfaceDeviceService')
BT_HARDCOPY_CABLE_REPLACEMENT_SERVICE                = UUID.from_16_bits(0x1125, 'HardcopyCableReplacement')
BT_HCR_PRINT_SERVICE                                 = UUID.from_16_bits(0x1126, 'HCR_Print')
BT_HCR_SCAN_SERVICE                                  = UUID.from_16_bits(0x1127, 'HCR_Scan')
BT_COMMON_ISDN_ACCESS_SERVICE                        = UUID.from_16_bits(0x1128, 'Common_ISDN_Access')
BT_SIM_ACCESS_SERVICE                                = UUID.from_16_bits(0x112D, 'SIM_Access')
BT_PHONEBOOK_ACCESS_PCE_SERVICE                      = UUID.from_16_bits(0x112E, 'Phonebook Access - PCE')
BT_PHONEBOOK_ACCESS_PSE_SERVICE                      = UUID.from_16_bits(0x112F, 'Phonebook Access - PSE')
BT_PHONEBOOK_ACCESS_SERVICE                          = UUID.from_16_bits(0x1130, 'Phonebook Access')
BT_HEADSET_HS_SERVICE                                = UUID.from_16_bits(0x1131, 'Headset - HS')
BT_MESSAGE_ACCESS_SERVER_SERVICE                     = UUID.from_16_bits(0x1132, 'Message Access Server')
BT_MESSAGE_NOTIFICATION_SERVER_SERVICE               = UUID.from_16_bits(0x1133, 'Message Notification Server')
BT_MESSAGE_ACCESS_PROFILE_SERVICE                    = UUID.from_16_bits(0x1134, 'Message Access Profile')
BT_GNSS_SERVICE                                      = UUID.from_16_bits(0x1135, 'GNSS')
BT_GNSS_SERVER_SERVICE                               = UUID.from_16_bits(0x1136, 'GNSS_Server')
BT_3D_DISPLAY_SERVICE                                = UUID.from_16_bits(0x1137, '3D Display')
BT_3D_GLASSES_SERVICE                                = UUID.from_16_bits(0x1138, '3D Glasses')
BT_3D_SYNCHRONIZATION_SERVICE                        = UUID.from_16_bits(0x1139, '3D Synchronization')
BT_MPS_PROFILE_SERVICE                               = UUID.from_16_bits(0x113A, 'MPS Profile')
BT_MPS_SC_SERVICE                                    = UUID.from_16_bits(0x113B, 'MPS SC')
BT_ACCESS_SERVICE_SERVICE                            = UUID.from_16_bits(0x113C, 'CTN Access Service')
BT_CTN_NOTIFICATION_SERVICE_SERVICE                  = UUID.from_16_bits(0x113D, 'CTN Notification Service')
BT_CTN_PROFILE_SERVICE                               = UUID.from_16_bits(0x113E, 'CTN Profile')
BT_PNP_INFORMATION_SERVICE                           = UUID.from_16_bits(0x1200, 'PnPInformation')
BT_GENERIC_NETWORKING_SERVICE                        = UUID.from_16_bits(0x1201, 'GenericNetworking')
BT_GENERIC_FILE_TRANSFER_SERVICE                     = UUID.from_16_bits(0x1202, 'GenericFileTransfer')
BT_GENERIC_AUDIO_SERVICE                             = UUID.from_16_bits(0x1203, 'GenericAudio')
BT_GENERIC_TELEPHONY_SERVICE                         = UUID.from_16_bits(0x1204, 'GenericTelephony')
BT_UPNP_SERVICE                                      = UUID.from_16_bits(0x1205, 'UPNP_Service')
BT_UPNP_IP_SERVICE                                   = UUID.from_16_bits(0x1206, 'UPNP_IP_Service')
BT_ESDP_UPNP_IP_PAN_SERVICE                          = UUID.from_16_bits(0x1300, 'ESDP_UPNP_IP_PAN')
BT_ESDP_UPNP_IP_LAP_SERVICE                          = UUID.from_16_bits(0x1301, 'ESDP_UPNP_IP_LAP')
BT_ESDP_UPNP_L2CAP_SERVICE                           = UUID.from_16_bits(0x1302, 'ESDP_UPNP_L2CAP')
BT_VIDEO_SOURCE_SERVICE                              = UUID.from_16_bits(0x1303, 'VideoSource')
BT_VIDEO_SINK_SERVICE                                = UUID.from_16_bits(0x1304, 'VideoSink')
BT_VIDEO_DISTRIBUTION_SERVICE                        = UUID.from_16_bits(0x1305, 'VideoDistribution')
BT_HDP_SERVICE                                       = UUID.from_16_bits(0x1400, 'HDP')
BT_HDP_SOURCE_SERVICE                                = UUID.from_16_bits(0x1401, 'HDP Source')
BT_HDP_SINK_SERVICE                                  = UUID.from_16_bits(0x1402, 'HDP Sink')

# fmt: on
# pylint: enable=line-too-long


# -----------------------------------------------------------------------------
# ClassOfDevice
# See Bluetooth - Assigned Numbers - 2.8 Class of Device
# -----------------------------------------------------------------------------
@dataclasses.dataclass
class ClassOfDevice:
    # fmt: off
    class MajorServiceClasses(utils.CompatibleIntFlag):
        LIMITED_DISCOVERABLE_MODE = (1 << 0)
        LE_AUDIO                  = (1 << 1)
        POSITIONING               = (1 << 3)
        NETWORKING                = (1 << 4)
        RENDERING                 = (1 << 5)
        CAPTURING                 = (1 << 6)
        OBJECT_TRANSFER           = (1 << 7)
        AUDIO                     = (1 << 8)
        TELEPHONY                 = (1 << 9)
        INFORMATION               = (1 << 10)

    MAJOR_SERVICE_CLASS_LABELS: ClassVar[list[str]] = [
        'Limited Discoverable Mode',
        'LE audio',
        '(reserved)',
        'Positioning',
        'Networking',
        'Rendering',
        'Capturing',
        'Object Transfer',
        'Audio',
        'Telephony',
        'Information',
    ]

    class MajorDeviceClass(utils.OpenIntEnum):
        MISCELLANEOUS            = 0x00
        COMPUTER                 = 0x01
        PHONE                    = 0x02
        LAN_NETWORK_ACCESS_POINT = 0x03
        AUDIO_VIDEO              = 0x04
        PERIPHERAL               = 0x05
        IMAGING                  = 0x06
        WEARABLE                 = 0x07
        TOY                      = 0x08
        HEALTH                   = 0x09
        UNCATEGORIZED            = 0x1F

    MAJOR_DEVICE_CLASS_LABELS: ClassVar[dict[MajorDeviceClass, str]] = {
        MajorDeviceClass.MISCELLANEOUS:            'Miscellaneous',
        MajorDeviceClass.COMPUTER:                 'Computer',
        MajorDeviceClass.PHONE:                    'Phone',
        MajorDeviceClass.LAN_NETWORK_ACCESS_POINT: 'LAN/Network Access Point',
        MajorDeviceClass.AUDIO_VIDEO:              'Audio/Video',
        MajorDeviceClass.PERIPHERAL:               'Peripheral',
        MajorDeviceClass.IMAGING:                  'Imaging',
        MajorDeviceClass.WEARABLE:                 'Wearable',
        MajorDeviceClass.TOY:                      'Toy',
        MajorDeviceClass.HEALTH:                   'Health',
        MajorDeviceClass.UNCATEGORIZED:            'Uncategorized',
    }

    class ComputerMinorDeviceClass(utils.OpenIntEnum):
        UNCATEGORIZED         = 0x00
        DESKTOP_WORKSTATION   = 0x01
        SERVER_CLASS_COMPUTER = 0x02
        LAPTOP_COMPUTER       = 0x03
        HANDHELD_PC_PDA       = 0x04
        PALM_SIZE_PC_PDA      = 0x05
        WEARABLE_COMPUTER     = 0x06
        TABLET                = 0x07

    COMPUTER_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[ComputerMinorDeviceClass, str]] = {
        ComputerMinorDeviceClass.UNCATEGORIZED:         'Uncategorized',
        ComputerMinorDeviceClass.DESKTOP_WORKSTATION:   'Desktop workstation',
        ComputerMinorDeviceClass.SERVER_CLASS_COMPUTER: 'Server-class computer',
        ComputerMinorDeviceClass.LAPTOP_COMPUTER:       'Laptop',
        ComputerMinorDeviceClass.HANDHELD_PC_PDA:       'Handheld PC/PDA',
        ComputerMinorDeviceClass.PALM_SIZE_PC_PDA:      'Palm-size PC/PDA',
        ComputerMinorDeviceClass.WEARABLE_COMPUTER:     'Wearable computer',
        ComputerMinorDeviceClass.TABLET:                'Tablet',
    }

    class PhoneMinorDeviceClass(utils.OpenIntEnum):
        UNCATEGORIZED                = 0x00
        CELLULAR                     = 0x01
        CORDLESS                     = 0x02
        SMARTPHONE                   = 0x03
        WIRED_MODEM_OR_VOICE_GATEWAY = 0x04
        COMMON_ISDN = 0x05

    PHONE_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[PhoneMinorDeviceClass, str]] = {
        PhoneMinorDeviceClass.UNCATEGORIZED:                'Uncategorized',
        PhoneMinorDeviceClass.CELLULAR:                     'Cellular',
        PhoneMinorDeviceClass.CORDLESS:                     'Cordless',
        PhoneMinorDeviceClass.SMARTPHONE:                   'Smartphone',
        PhoneMinorDeviceClass.WIRED_MODEM_OR_VOICE_GATEWAY: 'Wired modem or voice gateway',
        PhoneMinorDeviceClass.COMMON_ISDN:                  'Common ISDN access',
    }

    class LanNetworkMinorDeviceClass(utils.OpenIntEnum):
        FULLY_AVAILABLE            = 0x00
        _1_TO_17_PERCENT_UTILIZED  = 0x01
        _17_TO_33_PERCENT_UTILIZED = 0x02
        _33_TO_50_PERCENT_UTILIZED = 0x03
        _50_TO_67_PERCENT_UTILIZED = 0x04
        _67_TO_83_PERCENT_UTILIZED = 0x05
        _83_TO_99_PERCENT_UTILIZED = 0x06
        _NO_SERVICE_AVAILABLE      = 0x07

    LAN_NETWORK_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[LanNetworkMinorDeviceClass, str]] = {
        LanNetworkMinorDeviceClass.FULLY_AVAILABLE:            'Fully availbable',
        LanNetworkMinorDeviceClass._1_TO_17_PERCENT_UTILIZED:  '1% to 17% utilized',
        LanNetworkMinorDeviceClass._17_TO_33_PERCENT_UTILIZED: '17% to 33% utilized',
        LanNetworkMinorDeviceClass._33_TO_50_PERCENT_UTILIZED: '33% to 50% utilized',
        LanNetworkMinorDeviceClass._50_TO_67_PERCENT_UTILIZED: '50% to 67% utilized',
        LanNetworkMinorDeviceClass._67_TO_83_PERCENT_UTILIZED: '67% to 83% utilized',
        LanNetworkMinorDeviceClass._83_TO_99_PERCENT_UTILIZED: '83% to 99% utilized',
        LanNetworkMinorDeviceClass._NO_SERVICE_AVAILABLE:      'No service available',
    }

    class AudioVideoMinorDeviceClass(utils.OpenIntEnum):
        UNCATEGORIZED                 = 0x00
        WEARABLE_HEADSET_DEVICE       = 0x01
        HANDS_FREE_DEVICE             = 0x02
        # (RESERVED)                  = 0x03
        MICROPHONE                    = 0x04
        LOUDSPEAKER                   = 0x05
        HEADPHONES                    = 0x06
        PORTABLE_AUDIO                = 0x07
        CAR_AUDIO                     = 0x08
        SET_TOP_BOX                   = 0x09
        HIFI_AUDIO_DEVICE             = 0x0A
        VCR                           = 0x0B
        VIDEO_CAMERA                  = 0x0C
        CAMCORDER                     = 0x0D
        VIDEO_MONITOR                 = 0x0E
        VIDEO_DISPLAY_AND_LOUDSPEAKER = 0x0F
        VIDEO_CONFERENCING            = 0x10
        # (RESERVED)                  = 0x11
        GAMING_OR_TOY                 = 0x12

    AUDIO_VIDEO_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[AudioVideoMinorDeviceClass, str]] = {
        AudioVideoMinorDeviceClass.UNCATEGORIZED:                 'Uncategorized',
        AudioVideoMinorDeviceClass.WEARABLE_HEADSET_DEVICE:       'Wearable Headset Device',
        AudioVideoMinorDeviceClass.HANDS_FREE_DEVICE:             'Hands-free Device',
        AudioVideoMinorDeviceClass.MICROPHONE:                    'Microphone',
        AudioVideoMinorDeviceClass.LOUDSPEAKER:                   'Loudspeaker',
        AudioVideoMinorDeviceClass.HEADPHONES:                    'Headphones',
        AudioVideoMinorDeviceClass.PORTABLE_AUDIO:                'Portable Audio',
        AudioVideoMinorDeviceClass.CAR_AUDIO:                     'Car audio',
        AudioVideoMinorDeviceClass.SET_TOP_BOX:                   'Set-top box',
        AudioVideoMinorDeviceClass.HIFI_AUDIO_DEVICE:             'HiFi Audio Device',
        AudioVideoMinorDeviceClass.VCR:                           'VCR',
        AudioVideoMinorDeviceClass.VIDEO_CAMERA:                  'Video Camera',
        AudioVideoMinorDeviceClass.CAMCORDER:                     'Camcorder',
        AudioVideoMinorDeviceClass.VIDEO_MONITOR:                 'Video Monitor',
        AudioVideoMinorDeviceClass.VIDEO_DISPLAY_AND_LOUDSPEAKER: 'Video Display and Loudspeaker',
        AudioVideoMinorDeviceClass.VIDEO_CONFERENCING:            'Video Conferencing',
        AudioVideoMinorDeviceClass.GAMING_OR_TOY:                 'Gaming/Toy',
    }

    class PeripheralMinorDeviceClass(utils.OpenIntEnum):
        UNCATEGORIZED                  = 0x00
        KEYBOARD                       = 0x10
        POINTING_DEVICE                = 0x20
        COMBO_KEYBOARD_POINTING_DEVICE = 0x30
        JOYSTICK                       = 0x01
        GAMEPAD                        = 0x02
        REMOTE_CONTROL                 = 0x03
        SENSING_DEVICE                 = 0x04
        DIGITIZER_TABLET               = 0x05
        CARD_READER                    = 0x06
        DIGITAL_PEN                    = 0x07
        HANDHELD_SCANNER               = 0x08
        HANDHELD_GESTURAL_INPUT_DEVICE = 0x09

    PERIPHERAL_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[PeripheralMinorDeviceClass, str]] = {
        PeripheralMinorDeviceClass.UNCATEGORIZED:                  'Uncategorized',
        PeripheralMinorDeviceClass.KEYBOARD:                       'Keyboard',
        PeripheralMinorDeviceClass.POINTING_DEVICE:                'Pointing device',
        PeripheralMinorDeviceClass.COMBO_KEYBOARD_POINTING_DEVICE: 'Combo keyboard/pointing device',
        PeripheralMinorDeviceClass.JOYSTICK:                       'Joystick',
        PeripheralMinorDeviceClass.GAMEPAD:                        'Gamepad',
        PeripheralMinorDeviceClass.REMOTE_CONTROL:                 'Remote control',
        PeripheralMinorDeviceClass.SENSING_DEVICE:                 'Sensing device',
        PeripheralMinorDeviceClass.DIGITIZER_TABLET:               'Digitizer tablet',
        PeripheralMinorDeviceClass.CARD_READER:                    'Card Reader',
        PeripheralMinorDeviceClass.DIGITAL_PEN:                    'Digital Pen',
        PeripheralMinorDeviceClass.HANDHELD_SCANNER:               'Handheld scanner',
        PeripheralMinorDeviceClass.HANDHELD_GESTURAL_INPUT_DEVICE: 'Handheld gestural input device',
    }

    class WearableMinorDeviceClass(utils.OpenIntEnum):
        UNCATEGORIZED = 0x00
        WRISTWATCH    = 0x01
        PAGER         = 0x02
        JACKET        = 0x03
        HELMET        = 0x04
        GLASSES       = 0x05

    WEARABLE_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[WearableMinorDeviceClass, str]] = {
        WearableMinorDeviceClass.UNCATEGORIZED: 'Uncategorized',
        WearableMinorDeviceClass.WRISTWATCH:    'Wristwatch',
        WearableMinorDeviceClass.PAGER:         'Pager',
        WearableMinorDeviceClass.JACKET:        'Jacket',
        WearableMinorDeviceClass.HELMET:        'Helmet',
        WearableMinorDeviceClass.GLASSES:       'Glasses',
    }

    class ToyMinorDeviceClass(utils.OpenIntEnum):
        UNCATEGORIZED      = 0x00
        ROBOT              = 0x01
        VEHICLE            = 0x02
        DOLL_ACTION_FIGURE = 0x03
        CONTROLLER         = 0x04
        GAME               = 0x05

    TOY_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[ToyMinorDeviceClass, str]] = {
        ToyMinorDeviceClass.UNCATEGORIZED:      'Uncategorized',
        ToyMinorDeviceClass.ROBOT:              'Robot',
        ToyMinorDeviceClass.VEHICLE:            'Vehicle',
        ToyMinorDeviceClass.DOLL_ACTION_FIGURE: 'Doll/Action figure',
        ToyMinorDeviceClass.CONTROLLER:         'Controller',
        ToyMinorDeviceClass.GAME:               'Game',
    }

    class HealthMinorDeviceClass(utils.OpenIntEnum):
        UNDEFINED                 = 0x00
        BLOOD_PRESSURE_MONITOR    = 0x01
        THERMOMETER               = 0x02
        WEIGHING_SCALE            = 0x03
        GLUCOSE_METER             = 0x04
        PULSE_OXIMETER            = 0x05
        HEART_PULSE_RATE_MONITOR  = 0x06
        HEALTH_DATA_DISPLAY       = 0x07
        STEP_COUNTER              = 0x08
        BODY_COMPOSITION_ANALYZER = 0x09
        PEAK_FLOW_MONITOR         = 0x0A
        MEDICATION_MONITOR        = 0x0B
        KNEE_PROSTHESIS           = 0x0C
        ANKLE_PROSTHESIS          = 0x0D
        GENERIC_HEALTH_MANAGER    = 0x0E
        PERSONAL_MOBILITY_DEVICE  = 0x0F

    HEALTH_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[HealthMinorDeviceClass, str]] = {
        HealthMinorDeviceClass.UNDEFINED:                 'Undefined',
        HealthMinorDeviceClass.BLOOD_PRESSURE_MONITOR:    'Blood Pressure Monitor',
        HealthMinorDeviceClass.THERMOMETER:               'Thermometer',
        HealthMinorDeviceClass.WEIGHING_SCALE:            'Weighing Scale',
        HealthMinorDeviceClass.GLUCOSE_METER:             'Glucose Meter',
        HealthMinorDeviceClass.PULSE_OXIMETER:            'Pulse Oximeter',
        HealthMinorDeviceClass.HEART_PULSE_RATE_MONITOR:  'Heart/Pulse Rate Monitor',
        HealthMinorDeviceClass.HEALTH_DATA_DISPLAY:       'Health Data Display',
        HealthMinorDeviceClass.STEP_COUNTER:              'Step Counter',
        HealthMinorDeviceClass.BODY_COMPOSITION_ANALYZER: 'Body Composition Analyzer',
        HealthMinorDeviceClass.PEAK_FLOW_MONITOR:         'Peak Flow Monitor',
        HealthMinorDeviceClass.MEDICATION_MONITOR:        'Medication Monitor',
        HealthMinorDeviceClass.KNEE_PROSTHESIS:           'Knee Prosthesis',
        HealthMinorDeviceClass.ANKLE_PROSTHESIS:          'Ankle Prosthesis',
        HealthMinorDeviceClass.GENERIC_HEALTH_MANAGER:    'Generic Health Manager',
        HealthMinorDeviceClass.PERSONAL_MOBILITY_DEVICE:  'Personal Mobility Device',
    }

    MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[MajorDeviceClass, dict[Any, str]]] = {
        MajorDeviceClass.COMPUTER: COMPUTER_MINOR_DEVICE_CLASS_LABELS,
        MajorDeviceClass.PHONE: PHONE_MINOR_DEVICE_CLASS_LABELS,
        MajorDeviceClass.LAN_NETWORK_ACCESS_POINT: LAN_NETWORK_MINOR_DEVICE_CLASS_LABELS,
        MajorDeviceClass.AUDIO_VIDEO: AUDIO_VIDEO_MINOR_DEVICE_CLASS_LABELS,
        MajorDeviceClass.PERIPHERAL: PERIPHERAL_MINOR_DEVICE_CLASS_LABELS,
        MajorDeviceClass.WEARABLE: WEARABLE_MINOR_DEVICE_CLASS_LABELS,
        MajorDeviceClass.TOY: TOY_MINOR_DEVICE_CLASS_LABELS,
        MajorDeviceClass.HEALTH: HEALTH_MINOR_DEVICE_CLASS_LABELS,
    }

    _MINOR_DEVICE_CLASSES: ClassVar[dict[MajorDeviceClass, type]] = {
        MajorDeviceClass.COMPUTER: ComputerMinorDeviceClass,
        MajorDeviceClass.PHONE: PhoneMinorDeviceClass,
        MajorDeviceClass.LAN_NETWORK_ACCESS_POINT: LanNetworkMinorDeviceClass,
        MajorDeviceClass.AUDIO_VIDEO: AudioVideoMinorDeviceClass,
        MajorDeviceClass.PERIPHERAL: PeripheralMinorDeviceClass,
        MajorDeviceClass.WEARABLE: WearableMinorDeviceClass,
        MajorDeviceClass.TOY: ToyMinorDeviceClass,
        MajorDeviceClass.HEALTH: HealthMinorDeviceClass,
    }

    # fmt: on

    major_service_classes: MajorServiceClasses
    major_device_class: MajorDeviceClass
    minor_device_class: (
        ComputerMinorDeviceClass
        | PhoneMinorDeviceClass
        | LanNetworkMinorDeviceClass
        | AudioVideoMinorDeviceClass
        | PeripheralMinorDeviceClass
        | WearableMinorDeviceClass
        | ToyMinorDeviceClass
        | HealthMinorDeviceClass
        | int
    )

    @classmethod
    def from_int(cls, class_of_device: int) -> Self:
        major_service_classes = cls.MajorServiceClasses(class_of_device >> 13 & 0x7FF)
        major_device_class = cls.MajorDeviceClass(class_of_device >> 8 & 0x1F)
        minor_device_class_int = class_of_device >> 2 & 0x3F
        if minor_device_class_object := cls._MINOR_DEVICE_CLASSES.get(
            major_device_class
        ):
            minor_device_class = minor_device_class_object(minor_device_class_int)
        else:
            minor_device_class = minor_device_class_int
        return cls(major_service_classes, major_device_class, minor_device_class)

    def __int__(self) -> int:
        return (
            self.major_service_classes << 13
            | self.major_device_class << 8
            | self.minor_device_class << 2
        )

    def __str__(self) -> str:
        minor_device_class_name = (
            self.minor_device_class.name
            if hasattr(self.minor_device_class, 'name')
            else hex(self.minor_device_class)
        )
        return (
            f"ClassOfDevice({self.major_service_classes.composite_name},"
            f"{self.major_device_class.name}/{minor_device_class_name})"
        )

    def major_service_classes_labels(self) -> str:
        return "|".join(
            bit_flags_to_strings(
                self.major_service_classes, self.MAJOR_SERVICE_CLASS_LABELS
            )
        )

    def major_device_class_label(self) -> str:
        return name_or_number(
            cast(dict[int, str], self.MAJOR_DEVICE_CLASS_LABELS),
            self.major_device_class,
        )

    def minor_device_class_label(self) -> str:
        class_names = self.MINOR_DEVICE_CLASS_LABELS.get(self.major_device_class)
        if class_names is None:
            return f'#{self.minor_device_class:02X}'
        return name_or_number(class_names, self.minor_device_class)


# -----------------------------------------------------------------------------
# DeviceClass
# -----------------------------------------------------------------------------
class DeviceClass:
    """Legacy only. Use ClassOfDevice instead"""

    # fmt: off
    # pylint: disable=line-too-long

    # Major Service Classes (flags combined with OR)
    LIMITED_DISCOVERABLE_MODE_SERVICE_CLASS = ClassOfDevice.MajorServiceClasses.LIMITED_DISCOVERABLE_MODE
    LE_AUDIO_SERVICE_CLASS                  = ClassOfDevice.MajorServiceClasses.LE_AUDIO
    POSITIONING_SERVICE_CLASS               = ClassOfDevice.MajorServiceClasses.POSITIONING
    NETWORKING_SERVICE_CLASS                = ClassOfDevice.MajorServiceClasses.NETWORKING
    RENDERING_SERVICE_CLASS                 = ClassOfDevice.MajorServiceClasses.RENDERING
    CAPTURING_SERVICE_CLASS                 = ClassOfDevice.MajorServiceClasses.CAPTURING
    OBJECT_TRANSFER_SERVICE_CLASS           = ClassOfDevice.MajorServiceClasses.OBJECT_TRANSFER
    AUDIO_SERVICE_CLASS                     = ClassOfDevice.MajorServiceClasses.AUDIO
    TELEPHONY_SERVICE_CLASS                 = ClassOfDevice.MajorServiceClasses.TELEPHONY
    INFORMATION_SERVICE_CLASS               = ClassOfDevice.MajorServiceClasses.INFORMATION

    # Major Device Classes
    MISCELLANEOUS_MAJOR_DEVICE_CLASS            = ClassOfDevice.MajorDeviceClass.MISCELLANEOUS
    COMPUTER_MAJOR_DEVICE_CLASS                 = ClassOfDevice.MajorDeviceClass.COMPUTER
    PHONE_MAJOR_DEVICE_CLASS                    = ClassOfDevice.MajorDeviceClass.PHONE
    LAN_NETWORK_ACCESS_POINT_MAJOR_DEVICE_CLASS = ClassOfDevice.MajorDeviceClass.LAN_NETWORK_ACCESS_POINT
    AUDIO_VIDEO_MAJOR_DEVICE_CLASS              = ClassOfDevice.MajorDeviceClass.AUDIO_VIDEO
    PERIPHERAL_MAJOR_DEVICE_CLASS               = ClassOfDevice.MajorDeviceClass.PERIPHERAL
    IMAGING_MAJOR_DEVICE_CLASS                  = ClassOfDevice.MajorDeviceClass.IMAGING
    WEARABLE_MAJOR_DEVICE_CLASS                 = ClassOfDevice.MajorDeviceClass.WEARABLE
    TOY_MAJOR_DEVICE_CLASS                      = ClassOfDevice.MajorDeviceClass.TOY
    HEALTH_MAJOR_DEVICE_CLASS                   = ClassOfDevice.MajorDeviceClass.HEALTH
    UNCATEGORIZED_MAJOR_DEVICE_CLASS            = ClassOfDevice.MajorDeviceClass.UNCATEGORIZED

    COMPUTER_UNCATEGORIZED_MINOR_DEVICE_CLASS         = ClassOfDevice.ComputerMinorDeviceClass.UNCATEGORIZED
    COMPUTER_DESKTOP_WORKSTATION_MINOR_DEVICE_CLASS   = ClassOfDevice.ComputerMinorDeviceClass.DESKTOP_WORKSTATION
    COMPUTER_SERVER_CLASS_COMPUTER_MINOR_DEVICE_CLASS = ClassOfDevice.ComputerMinorDeviceClass.SERVER_CLASS_COMPUTER
    COMPUTER_LAPTOP_COMPUTER_MINOR_DEVICE_CLASS       = ClassOfDevice.ComputerMinorDeviceClass.LAPTOP_COMPUTER
    COMPUTER_HANDHELD_PC_PDA_MINOR_DEVICE_CLASS       = ClassOfDevice.ComputerMinorDeviceClass.HANDHELD_PC_PDA
    COMPUTER_PALM_SIZE_PC_PDA_MINOR_DEVICE_CLASS      = ClassOfDevice.ComputerMinorDeviceClass.PALM_SIZE_PC_PDA
    COMPUTER_WEARABLE_COMPUTER_MINOR_DEVICE_CLASS     = ClassOfDevice.ComputerMinorDeviceClass.WEARABLE_COMPUTER
    COMPUTER_TABLET_MINOR_DEVICE_CLASS                = ClassOfDevice.ComputerMinorDeviceClass.TABLET

    PHONE_UNCATEGORIZED_MINOR_DEVICE_CLASS                = ClassOfDevice.PhoneMinorDeviceClass.UNCATEGORIZED
    PHONE_CELLULAR_MINOR_DEVICE_CLASS                     = ClassOfDevice.PhoneMinorDeviceClass.CELLULAR
    PHONE_CORDLESS_MINOR_DEVICE_CLASS                     = ClassOfDevice.PhoneMinorDeviceClass.CORDLESS
    PHONE_SMARTPHONE_MINOR_DEVICE_CLASS                   = ClassOfDevice.PhoneMinorDeviceClass.SMARTPHONE
    PHONE_WIRED_MODEM_OR_VOICE_GATEWAY_MINOR_DEVICE_CLASS = ClassOfDevice.PhoneMinorDeviceClass.WIRED_MODEM_OR_VOICE_GATEWAY
    PHONE_COMMON_ISDN_MINOR_DEVICE_CLASS                  = ClassOfDevice.PhoneMinorDeviceClass.COMMON_ISDN

    AUDIO_VIDEO_UNCATEGORIZED_MINOR_DEVICE_CLASS                 = ClassOfDevice.AudioVideoMinorDeviceClass.UNCATEGORIZED
    AUDIO_VIDEO_WEARABLE_HEADSET_DEVICE_MINOR_DEVICE_CLASS       = ClassOfDevice.AudioVideoMinorDeviceClass.WEARABLE_HEADSET_DEVICE
    AUDIO_VIDEO_HANDS_FREE_DEVICE_MINOR_DEVICE_CLASS             = ClassOfDevice.AudioVideoMinorDeviceClass.HANDS_FREE_DEVICE
    AUDIO_VIDEO_MICROPHONE_MINOR_DEVICE_CLASS                    = ClassOfDevice.AudioVideoMinorDeviceClass.MICROPHONE
    AUDIO_VIDEO_LOUDSPEAKER_MINOR_DEVICE_CLASS                   = ClassOfDevice.AudioVideoMinorDeviceClass.LOUDSPEAKER
    AUDIO_VIDEO_HEADPHONES_MINOR_DEVICE_CLASS                    = ClassOfDevice.AudioVideoMinorDeviceClass.HEADPHONES
    AUDIO_VIDEO_PORTABLE_AUDIO_MINOR_DEVICE_CLASS                = ClassOfDevice.AudioVideoMinorDeviceClass.PORTABLE_AUDIO
    AUDIO_VIDEO_CAR_AUDIO_MINOR_DEVICE_CLASS                     = ClassOfDevice.AudioVideoMinorDeviceClass.CAR_AUDIO
    AUDIO_VIDEO_SET_TOP_BOX_MINOR_DEVICE_CLASS                   = ClassOfDevice.AudioVideoMinorDeviceClass.SET_TOP_BOX
    AUDIO_VIDEO_HIFI_AUDIO_DEVICE_MINOR_DEVICE_CLASS             = ClassOfDevice.AudioVideoMinorDeviceClass.HIFI_AUDIO_DEVICE
    AUDIO_VIDEO_VCR_MINOR_DEVICE_CLASS                           = ClassOfDevice.AudioVideoMinorDeviceClass.VCR
    AUDIO_VIDEO_VIDEO_CAMERA_MINOR_DEVICE_CLASS                  = ClassOfDevice.AudioVideoMinorDeviceClass.VIDEO_CAMERA
    AUDIO_VIDEO_CAMCORDER_MINOR_DEVICE_CLASS                     = ClassOfDevice.AudioVideoMinorDeviceClass.CAMCORDER
    AUDIO_VIDEO_VIDEO_MONITOR_MINOR_DEVICE_CLASS                 = ClassOfDevice.AudioVideoMinorDeviceClass.VIDEO_MONITOR
    AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.VIDEO_DISPLAY_AND_LOUDSPEAKER
    AUDIO_VIDEO_VIDEO_CONFERENCING_MINOR_DEVICE_CLASS            = ClassOfDevice.AudioVideoMinorDeviceClass.VIDEO_CONFERENCING
    AUDIO_VIDEO_GAMING_OR_TOY_MINOR_DEVICE_CLASS                 = ClassOfDevice.AudioVideoMinorDeviceClass.GAMING_OR_TOY

    PERIPHERAL_UNCATEGORIZED_MINOR_DEVICE_CLASS                  = ClassOfDevice.PeripheralMinorDeviceClass.UNCATEGORIZED
    PERIPHERAL_KEYBOARD_MINOR_DEVICE_CLASS                       = ClassOfDevice.PeripheralMinorDeviceClass.KEYBOARD
    PERIPHERAL_POINTING_DEVICE_MINOR_DEVICE_CLASS                = ClassOfDevice.PeripheralMinorDeviceClass.POINTING_DEVICE
    PERIPHERAL_COMBO_KEYBOARD_POINTING_DEVICE_MINOR_DEVICE_CLASS = ClassOfDevice.PeripheralMinorDeviceClass.COMBO_KEYBOARD_POINTING_DEVICE
    PERIPHERAL_JOYSTICK_MINOR_DEVICE_CLASS                       = ClassOfDevice.PeripheralMinorDeviceClass.JOYSTICK
    PERIPHERAL_GAMEPAD_MINOR_DEVICE_CLASS                        = ClassOfDevice.PeripheralMinorDeviceClass.GAMEPAD
    PERIPHERAL_REMOTE_CONTROL_MINOR_DEVICE_CLASS                 = ClassOfDevice.PeripheralMinorDeviceClass.REMOTE_CONTROL
    PERIPHERAL_SENSING_DEVICE_MINOR_DEVICE_CLASS                 = ClassOfDevice.PeripheralMinorDeviceClass.SENSING_DEVICE
    PERIPHERAL_DIGITIZER_TABLET_MINOR_DEVICE_CLASS               = ClassOfDevice.PeripheralMinorDeviceClass.DIGITIZER_TABLET
    PERIPHERAL_CARD_READER_MINOR_DEVICE_CLASS                    = ClassOfDevice.PeripheralMinorDeviceClass.CARD_READER
    PERIPHERAL_DIGITAL_PEN_MINOR_DEVICE_CLASS                    = ClassOfDevice.PeripheralMinorDeviceClass.DIGITAL_PEN
    PERIPHERAL_HANDHELD_SCANNER_MINOR_DEVICE_CLASS               = ClassOfDevice.PeripheralMinorDeviceClass.HANDHELD_SCANNER
    PERIPHERAL_HANDHELD_GESTURAL_INPUT_DEVICE_MINOR_DEVICE_CLASS = ClassOfDevice.PeripheralMinorDeviceClass.HANDHELD_GESTURAL_INPUT_DEVICE

    WEARABLE_UNCATEGORIZED_MINOR_DEVICE_CLASS = ClassOfDevice.WearableMinorDeviceClass.UNCATEGORIZED
    WEARABLE_WRISTWATCH_MINOR_DEVICE_CLASS    = ClassOfDevice.WearableMinorDeviceClass.WRISTWATCH
    WEARABLE_PAGER_MINOR_DEVICE_CLASS         = ClassOfDevice.WearableMinorDeviceClass.PAGER
    WEARABLE_JACKET_MINOR_DEVICE_CLASS        = ClassOfDevice.WearableMinorDeviceClass.JACKET
    WEARABLE_HELMET_MINOR_DEVICE_CLASS        = ClassOfDevice.WearableMinorDeviceClass.HELMET
    WEARABLE_GLASSES_MINOR_DEVICE_CLASS       = ClassOfDevice.WearableMinorDeviceClass.GLASSES

    TOY_UNCATEGORIZED_MINOR_DEVICE_CLASS      = ClassOfDevice.ToyMinorDeviceClass.UNCATEGORIZED
    TOY_ROBOT_MINOR_DEVICE_CLASS              = ClassOfDevice.ToyMinorDeviceClass.ROBOT
    TOY_VEHICLE_MINOR_DEVICE_CLASS            = ClassOfDevice.ToyMinorDeviceClass.VEHICLE
    TOY_DOLL_ACTION_FIGURE_MINOR_DEVICE_CLASS = ClassOfDevice.ToyMinorDeviceClass.DOLL_ACTION_FIGURE
    TOY_CONTROLLER_MINOR_DEVICE_CLASS         = ClassOfDevice.ToyMinorDeviceClass.CONTROLLER
    TOY_GAME_MINOR_DEVICE_CLASS               = ClassOfDevice.ToyMinorDeviceClass.GAME

    HEALTH_UNDEFINED_MINOR_DEVICE_CLASS                 = ClassOfDevice.HealthMinorDeviceClass.UNDEFINED
    HEALTH_BLOOD_PRESSURE_MONITOR_MINOR_DEVICE_CLASS    = ClassOfDevice.HealthMinorDeviceClass.BLOOD_PRESSURE_MONITOR
    HEALTH_THERMOMETER_MINOR_DEVICE_CLASS               = ClassOfDevice.HealthMinorDeviceClass.THERMOMETER
    HEALTH_WEIGHING_SCALE_MINOR_DEVICE_CLASS            = ClassOfDevice.HealthMinorDeviceClass.WEIGHING_SCALE
    HEALTH_GLUCOSE_METER_MINOR_DEVICE_CLASS             = ClassOfDevice.HealthMinorDeviceClass.GLUCOSE_METER
    HEALTH_PULSE_OXIMETER_MINOR_DEVICE_CLASS            = ClassOfDevice.HealthMinorDeviceClass.PULSE_OXIMETER
    HEALTH_HEART_PULSE_RATE_MONITOR_MINOR_DEVICE_CLASS  = ClassOfDevice.HealthMinorDeviceClass.HEART_PULSE_RATE_MONITOR
    HEALTH_HEALTH_DATA_DISPLAY_MINOR_DEVICE_CLASS       = ClassOfDevice.HealthMinorDeviceClass.HEALTH_DATA_DISPLAY
    HEALTH_STEP_COUNTER_MINOR_DEVICE_CLASS              = ClassOfDevice.HealthMinorDeviceClass.STEP_COUNTER
    HEALTH_BODY_COMPOSITION_ANALYZER_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.BODY_COMPOSITION_ANALYZER
    HEALTH_PEAK_FLOW_MONITOR_MINOR_DEVICE_CLASS         = ClassOfDevice.HealthMinorDeviceClass.PEAK_FLOW_MONITOR
    HEALTH_MEDICATION_MONITOR_MINOR_DEVICE_CLASS        = ClassOfDevice.HealthMinorDeviceClass.MEDICATION_MONITOR
    HEALTH_KNEE_PROSTHESIS_MINOR_DEVICE_CLASS           = ClassOfDevice.HealthMinorDeviceClass.KNEE_PROSTHESIS
    HEALTH_ANKLE_PROSTHESIS_MINOR_DEVICE_CLASS          = ClassOfDevice.HealthMinorDeviceClass.ANKLE_PROSTHESIS
    HEALTH_GENERIC_HEALTH_MANAGER_MINOR_DEVICE_CLASS    = ClassOfDevice.HealthMinorDeviceClass.GENERIC_HEALTH_MANAGER
    HEALTH_PERSONAL_MOBILITY_DEVICE_MINOR_DEVICE_CLASS  = ClassOfDevice.HealthMinorDeviceClass.PERSONAL_MOBILITY_DEVICE

    # fmt: on
    # pylint: enable=line-too-long

    @staticmethod
    def split_class_of_device(class_of_device: int) -> tuple[int, int, int]:
        # Split the bit fields of the composite class of device value into:
        # (service_classes, major_device_class, minor_device_class)
        return (
            (class_of_device >> 13 & 0x7FF),
            (class_of_device >> 8 & 0x1F),
            (class_of_device >> 2 & 0x3F),
        )

    @staticmethod
    def pack_class_of_device(service_classes, major_device_class, minor_device_class):
        return service_classes << 13 | major_device_class << 8 | minor_device_class << 2

    @staticmethod
    def service_class_labels(service_class_flags):
        return bit_flags_to_strings(
            service_class_flags, ClassOfDevice.MAJOR_SERVICE_CLASS_LABELS
        )

    @staticmethod
    def major_device_class_name(device_class):
        return name_or_number(ClassOfDevice.MAJOR_DEVICE_CLASS_LABELS, device_class)

    @staticmethod
    def minor_device_class_name(major_device_class, minor_device_class):
        class_names = ClassOfDevice.MINOR_DEVICE_CLASS_LABELS.get(major_device_class)
        if class_names is None:
            return f'#{minor_device_class:02X}'
        return name_or_number(class_names, minor_device_class)


# -----------------------------------------------------------------------------
# Appearance
# -----------------------------------------------------------------------------
class Appearance:
    class Category(utils.OpenIntEnum):
        UNKNOWN = 0x0000
        PHONE = 0x0001
        COMPUTER = 0x0002
        WATCH = 0x0003
        CLOCK = 0x0004
        DISPLAY = 0x0005
        REMOTE_CONTROL = 0x0006
        EYE_GLASSES = 0x0007
        TAG = 0x0008
        KEYRING = 0x0009
        MEDIA_PLAYER = 0x000A
        BARCODE_SCANNER = 0x000B
        THERMOMETER = 0x000C
        HEART_RATE_SENSOR = 0x000D
        BLOOD_PRESSURE = 0x000E
        HUMAN_INTERFACE_DEVICE = 0x000F
        GLUCOSE_METER = 0x0010
        RUNNING_WALKING_SENSOR = 0x0011
        CYCLING = 0x0012
        CONTROL_DEVICE = 0x0013
        NETWORK_DEVICE = 0x0014
        SENSOR = 0x0015
        LIGHT_FIXTURES = 0x0016
        FAN = 0x0017
        HVAC = 0x0018
        AIR_CONDITIONING = 0x0019
        HUMIDIFIER = 0x001A
        HEATING = 0x001B
        ACCESS_CONTROL = 0x001C
        MOTORIZED_DEVICE = 0x001D
        POWER_DEVICE = 0x001E
        LIGHT_SOURCE = 0x001F
        WINDOW_COVERING = 0x0020
        AUDIO_SINK = 0x0021
        AUDIO_SOURCE = 0x0022
        MOTORIZED_VEHICLE = 0x0023
        DOMESTIC_APPLIANCE = 0x0024
        WEARABLE_AUDIO_DEVICE = 0x0025
        AIRCRAFT = 0x0026
        AV_EQUIPMENT = 0x0027
        DISPLAY_EQUIPMENT = 0x0028
        HEARING_AID = 0x0029
        GAMING = 0x002A
        SIGNAGE = 0x002B
        PULSE_OXIMETER = 0x0031
        WEIGHT_SCALE = 0x0032
        PERSONAL_MOBILITY_DEVICE = 0x0033
        CONTINUOUS_GLUCOSE_MONITOR = 0x0034
        INSULIN_PUMP = 0x0035
        MEDICATION_DELIVERY = 0x0036
        SPIROMETER = 0x0037
        OUTDOOR_SPORTS_ACTIVITY = 0x0051

    class UnknownSubcategory(utils.OpenIntEnum):
        GENERIC_UNKNOWN = 0x00

    class PhoneSubcategory(utils.OpenIntEnum):
        GENERIC_PHONE = 0x00

    class ComputerSubcategory(utils.OpenIntEnum):
        GENERIC_COMPUTER = 0x00
        DESKTOP_WORKSTATION = 0x01
        SERVER_CLASS_COMPUTER = 0x02
        LAPTOP = 0x03
        HANDHELD_PC_PDA = 0x04
        PALM_SIZE_PC_PDA = 0x05
        WEARABLE_COMPUTER = 0x06
        TABLET = 0x07
        DOCKING_STATION = 0x08
        ALL_IN_ONE = 0x09
        BLADE_SERVER = 0x0A
        CONVERTIBLE = 0x0B
        DETACHABLE = 0x0C
        IOT_GATEWAY = 0x0D
        MINI_PC = 0x0E
        STICK_PC = 0x0F

    class WatchSubcategory(utils.OpenIntEnum):
        GENERIC_WATCH = 0x00
        SPORTS_WATCH = 0x01
        SMARTWATCH = 0x02

    class ClockSubcategory(utils.OpenIntEnum):
        GENERIC_CLOCK = 0x00

    class DisplaySubcategory(utils.OpenIntEnum):
        GENERIC_DISPLAY = 0x00

    class RemoteControlSubcategory(utils.OpenIntEnum):
        GENERIC_REMOTE_CONTROL = 0x00

    class EyeglassesSubcategory(utils.OpenIntEnum):
        GENERIC_EYEGLASSES = 0x00

    class TagSubcategory(utils.OpenIntEnum):
        GENERIC_TAG = 0x00

    class KeyringSubcategory(utils.OpenIntEnum):
        GENERIC_KEYRING = 0x00

    class MediaPlayerSubcategory(utils.OpenIntEnum):
        GENERIC_MEDIA_PLAYER = 0x00

    class BarcodeScannerSubcategory(utils.OpenIntEnum):
        GENERIC_BARCODE_SCANNER = 0x00

    class ThermometerSubcategory(utils.OpenIntEnum):
        GENERIC_THERMOMETER = 0x00
        EAR_THERMOMETER = 0x01

    class HeartRateSensorSubcategory(utils.OpenIntEnum):
        GENERIC_HEART_RATE_SENSOR = 0x00
        HEART_RATE_BELT = 0x01

    class BloodPressureSubcategory(utils.OpenIntEnum):
        GENERIC_BLOOD_PRESSURE = 0x00
        ARM_BLOOD_PRESSURE = 0x01
        WRIST_BLOOD_PRESSURE = 0x02

    class HumanInterfaceDeviceSubcategory(utils.OpenIntEnum):
        GENERIC_HUMAN_INTERFACE_DEVICE = 0x00
        KEYBOARD = 0x01
        MOUSE = 0x02
        JOYSTICK = 0x03
        GAMEPAD = 0x04
        DIGITIZER_TABLET = 0x05
        CARD_READER = 0x06
        DIGITAL_PEN = 0x07
        BARCODE_SCANNER = 0x08
        TOUCHPAD = 0x09
        PRESENTATION_REMOTE = 0x0A

    class GlucoseMeterSubcategory(utils.OpenIntEnum):
        GENERIC_GLUCOSE_METER = 0x00

    class RunningWalkingSensorSubcategory(utils.OpenIntEnum):
        GENERIC_RUNNING_WALKING_SENSOR = 0x00
        IN_SHOE_RUNNING_WALKING_SENSOR = 0x01
        ON_SHOW_RUNNING_WALKING_SENSOR = 0x02
        ON_HIP_RUNNING_WALKING_SENSOR = 0x03

    class CyclingSubcategory(utils.OpenIntEnum):
        GENERIC_CYCLING = 0x00
        CYCLING_COMPUTER = 0x01
        SPEED_SENSOR = 0x02
        CADENCE_SENSOR = 0x03
        POWER_SENSOR = 0x04
        SPEED_AND_CADENCE_SENSOR = 0x05

    class ControlDeviceSubcategory(utils.OpenIntEnum):
        GENERIC_CONTROL_DEVICE = 0x00
        SWITCH = 0x01
        MULTI_SWITCH = 0x02
        BUTTON = 0x03
        SLIDER = 0x04
        ROTARY_SWITCH = 0x05
        TOUCH_PANEL = 0x06
        SINGLE_SWITCH = 0x07
        DOUBLE_SWITCH = 0x08
        TRIPLE_SWITCH = 0x09
        BATTERY_SWITCH = 0x0A
        ENERGY_HARVESTING_SWITCH = 0x0B
        PUSH_BUTTON = 0x0C

    class NetworkDeviceSubcategory(utils.OpenIntEnum):
        GENERIC_NETWORK_DEVICE = 0x00
        ACCESS_POINT = 0x01
        MESH_DEVICE = 0x02
        MESH_NETWORK_PROXY = 0x03

    class SensorSubcategory(utils.OpenIntEnum):
        GENERIC_SENSOR = 0x00
        MOTION_SENSOR = 0x01
        AIR_QUALITY_SENSOR = 0x02
        TEMPERATURE_SENSOR = 0x03
        HUMIDITY_SENSOR = 0x04
        LEAK_SENSOR = 0x05
        SMOKE_SENSOR = 0x06
        OCCUPANCY_SENSOR = 0x07
        CONTACT_SENSOR = 0x08
        CARBON_MONOXIDE_SENSOR = 0x09
        CARBON_DIOXIDE_SENSOR = 0x0A
        AMBIENT_LIGHT_SENSOR = 0x0B
        ENERGY_SENSOR = 0x0C
        COLOR_LIGHT_SENSOR = 0x0D
        RAIN_SENSOR = 0x0E
        FIRE_SENSOR = 0x0F
        WIND_SENSOR = 0x10
        PROXIMITY_SENSOR = 0x11
        MULTI_SENSOR = 0x12
        FLUSH_MOUNTED_SENSOR = 0x13
        CEILING_MOUNTED_SENSOR = 0x14
        WALL_MOUNTED_SENSOR = 0x15
        MULTISENSOR = 0x16
        ENERGY_METER = 0x17
        FLAME_DETECTOR = 0x18
        VEHICLE_TIRE_PRESSURE_SENSOR = 0x19

    class LightFixturesSubcategory(utils.OpenIntEnum):
        GENERIC_LIGHT_FIXTURES = 0x00
        WALL_LIGHT = 0x01
        CEILING_LIGHT = 0x02
        FLOOR_LIGHT = 0x03
        CABINET_LIGHT = 0x04
        DESK_LIGHT = 0x05
        TROFFER_LIGHT = 0x06
        PENDANT_LIGHT = 0x07
        IN_GROUND_LIGHT = 0x08
        FLOOD_LIGHT = 0x09
        UNDERWATER_LIGHT = 0x0A
        BOLLARD_WITH_LIGHT = 0x0B
        PATHWAY_LIGHT = 0x0C
        GARDEN_LIGHT = 0x0D
        POLE_TOP_LIGHT = 0x0E
        SPOTLIGHT = 0x0F
        LINEAR_LIGHT = 0x10
        STREET_LIGHT = 0x11
        SHELVES_LIGHT = 0x12
        BAY_LIGHT = 0x013
        EMERGENCY_EXIT_LIGHT = 0x14
        LIGHT_CONTROLLER = 0x15
        LIGHT_DRIVER = 0x16
        BULB = 0x17
        LOW_BAY_LIGHT = 0x18
        HIGH_BAY_LIGHT = 0x19

    class FanSubcategory(utils.OpenIntEnum):
        GENERIC_FAN = 0x00
        CEILING_FAN = 0x01
        AXIAL_FAN = 0x02
        EXHAUST_FAN = 0x03
        PEDESTAL_FAN = 0x04
        DESK_FAN = 0x05
        WALL_FAN = 0x06

    class HvacSubcategory(utils.OpenIntEnum):
        GENERIC_HVAC = 0x00
        THERMOSTAT = 0x01
        HUMIDIFIER = 0x02
        DEHUMIDIFIER = 0x03
        HEATER = 0x04
        RADIATOR = 0x05
        BOILER = 0x06
        HEAT_PUMP = 0x07
        INFRARED_HEATER = 0x08
        RADIANT_PANEL_HEATER = 0x09
        FAN_HEATER = 0x0A
        AIR_CURTAIN = 0x0B

    class AirConditioningSubcategory(utils.OpenIntEnum):
        GENERIC_AIR_CONDITIONING = 0x00

    class HumidifierSubcategory(utils.OpenIntEnum):
        GENERIC_HUMIDIFIER = 0x00

    class HeatingSubcategory(utils.OpenIntEnum):
        GENERIC_HEATING = 0x00
        RADIATOR = 0x01
        BOILER = 0x02
        HEAT_PUMP = 0x03
        INFRARED_HEATER = 0x04
        RADIANT_PANEL_HEATER = 0x05
        FAN_HEATER = 0x06
        AIR_CURTAIN = 0x07

    class AccessControlSubcategory(utils.OpenIntEnum):
        GENERIC_ACCESS_CONTROL = 0x00
        ACCESS_DOOR = 0x01
        GARAGE_DOOR = 0x02
        EMERGENCY_EXIT_DOOR = 0x03
        ACCESS_LOCK = 0x04
        ELEVATOR = 0x05
        WINDOW = 0x06
        ENTRANCE_GATE = 0x07
        DOOR_LOCK = 0x08
        LOCKER = 0x09

    class MotorizedDeviceSubcategory(utils.OpenIntEnum):
        GENERIC_MOTORIZED_DEVICE = 0x00
        MOTORIZED_GATE = 0x01
        AWNING = 0x02
        BLINDS_OR_SHADES = 0x03
        CURTAINS = 0x04
        SCREEN = 0x05

    class PowerDeviceSubcategory(utils.OpenIntEnum):
        GENERIC_POWER_DEVICE = 0x00
        POWER_OUTLET = 0x01
        POWER_STRIP = 0x02
        PLUG = 0x03
        POWER_SUPPLY = 0x04
        LED_DRIVER = 0x05
        FLUORESCENT_LAMP_GEAR = 0x06
        HID_LAMP_GEAR = 0x07
        CHARGE_CASE = 0x08
        POWER_BANK = 0x09

    class LightSourceSubcategory(utils.OpenIntEnum):
        GENERIC_LIGHT_SOURCE = 0x00
        INCANDESCENT_LIGHT_BULB = 0x01
        LED_LAMP = 0x02
        HID_LAMP = 0x03
        FLUORESCENT_LAMP = 0x04
        LED_ARRAY = 0x05
        MULTI_COLOR_LED_ARRAY = 0x06
        LOW_VOLTAGE_HALOGEN = 0x07
        ORGANIC_LIGHT_EMITTING_DIODE = 0x08

    class WindowCoveringSubcategory(utils.OpenIntEnum):
        GENERIC_WINDOW_COVERING = 0x00
        WINDOW_SHADES = 0x01
        WINDOW_BLINDS = 0x02
        WINDOW_AWNING = 0x03
        WINDOW_CURTAIN = 0x04
        EXTERIOR_SHUTTER = 0x05
        EXTERIOR_SCREEN = 0x06

    class AudioSinkSubcategory(utils.OpenIntEnum):
        GENERIC_AUDIO_SINK = 0x00
        STANDALONE_SPEAKER = 0x01
        SOUNDBAR = 0x02
        BOOKSHELF_SPEAKER = 0x03
        STANDMOUNTED_SPEAKER = 0x04
        SPEAKERPHONE = 0x05

    class AudioSourceSubcategory(utils.OpenIntEnum):
        GENERIC_AUDIO_SOURCE = 0x00
        MICROPHONE = 0x01
        ALARM = 0x02
        BELL = 0x03
        HORN = 0x04
        BROADCASTING_DEVICE = 0x05
        SERVICE_DESK = 0x06
        KIOSK = 0x07
        BROADCASTING_ROOM = 0x08
        AUDITORIUM = 0x09

    class MotorizedVehicleSubcategory(utils.OpenIntEnum):
        GENERIC_MOTORIZED_VEHICLE = 0x00
        CAR = 0x01
        LARGE_GOODS_VEHICLE = 0x02
        TWO_WHEELED_VEHICLE = 0x03
        MOTORBIKE = 0x04
        SCOOTER = 0x05
        MOPED = 0x06
        THREE_WHEELED_VEHICLE = 0x07
        LIGHT_VEHICLE = 0x08
        QUAD_BIKE = 0x09
        MINIBUS = 0x0A
        BUS = 0x0B
        TROLLEY = 0x0C
        AGRICULTURAL_VEHICLE = 0x0D
        CAMPER_CARAVAN = 0x0E
        RECREATIONAL_VEHICLE_MOTOR_HOME = 0x0F

    class DomesticApplianceSubcategory(utils.OpenIntEnum):
        GENERIC_DOMESTIC_APPLIANCE = 0x00
        REFRIGERATOR = 0x01
        FREEZER = 0x02
        OVEN = 0x03
        MICROWAVE = 0x04
        TOASTER = 0x05
        WASHING_MACHINE = 0x06
        DRYER = 0x07
        COFFEE_MAKER = 0x08
        CLOTHES_IRON = 0x09
        CURLING_IRON = 0x0A
        HAIR_DRYER = 0x0B
        VACUUM_CLEANER = 0x0C
        ROBOTIC_VACUUM_CLEANER = 0x0D
        RICE_COOKER = 0x0E
        CLOTHES_STEAMER = 0x0F

    class WearableAudioDeviceSubcategory(utils.OpenIntEnum):
        GENERIC_WEARABLE_AUDIO_DEVICE = 0x00
        EARBUD = 0x01
        HEADSET = 0x02
        HEADPHONES = 0x03
        NECK_BAND = 0x04

    class AircraftSubcategory(utils.OpenIntEnum):
        GENERIC_AIRCRAFT = 0x00
        LIGHT_AIRCRAFT = 0x01
        MICROLIGHT = 0x02
        PARAGLIDER = 0x03
        LARGE_PASSENGER_AIRCRAFT = 0x04

    class AvEquipmentSubcategory(utils.OpenIntEnum):
        GENERIC_AV_EQUIPMENT = 0x00
        AMPLIFIER = 0x01
        RECEIVER = 0x02
        RADIO = 0x03
        TUNER = 0x04
        TURNTABLE = 0x05
        CD_PLAYER = 0x06
        DVD_PLAYER = 0x07
        BLURAY_PLAYER = 0x08
        OPTICAL_DISC_PLAYER = 0x09
        SET_TOP_BOX = 0x0A

    class DisplayEquipmentSubcategory(utils.OpenIntEnum):
        GENERIC_DISPLAY_EQUIPMENT = 0x00
        TELEVISION = 0x01
        MONITOR = 0x02
        PROJECTOR = 0x03

    class HearingAidSubcategory(utils.OpenIntEnum):
        GENERIC_HEARING_AID = 0x00
        IN_EAR_HEARING_AID = 0x01
        BEHIND_EAR_HEARING_AID = 0x02
        COCHLEAR_IMPLANT = 0x03

    class GamingSubcategory(utils.OpenIntEnum):
        GENERIC_GAMING = 0x00
        HOME_VIDEO_GAME_CONSOLE = 0x01
        PORTABLE_HANDHELD_CONSOLE = 0x02

    class SignageSubcategory(utils.OpenIntEnum):
        GENERIC_SIGNAGE = 0x00
        DIGITAL_SIGNAGE = 0x01
        ELECTRONIC_LABEL = 0x02

    class PulseOximeterSubcategory(utils.OpenIntEnum):
        GENERIC_PULSE_OXIMETER = 0x00
        FINGERTIP_PULSE_OXIMETER = 0x01
        WRIST_WORN_PULSE_OXIMETER = 0x02

    class WeightScaleSubcategory(utils.OpenIntEnum):
        GENERIC_WEIGHT_SCALE = 0x00

    class PersonalMobilityDeviceSubcategory(utils.OpenIntEnum):
        GENERIC_PERSONAL_MOBILITY_DEVICE = 0x00
        POWERED_WHEELCHAIR = 0x01
        MOBILITY_SCOOTER = 0x02

    class ContinuousGlucoseMonitorSubcategory(utils.OpenIntEnum):
        GENERIC_CONTINUOUS_GLUCOSE_MONITOR = 0x00

    class InsulinPumpSubcategory(utils.OpenIntEnum):
        GENERIC_INSULIN_PUMP = 0x00
        INSULIN_PUMP_DURABLE_PUMP = 0x01
        INSULIN_PUMP_PATCH_PUMP = 0x02
        INSULIN_PEN = 0x03

    class MedicationDeliverySubcategory(utils.OpenIntEnum):
        GENERIC_MEDICATION_DELIVERY = 0x00

    class SpirometerSubcategory(utils.OpenIntEnum):
        GENERIC_SPIROMETER = 0x00
        HANDHELD_SPIROMETER = 0x01

    class OutdoorSportsActivitySubcategory(utils.OpenIntEnum):
        GENERIC_OUTDOOR_SPORTS_ACTIVITY = 0x00
        LOCATION_DISPLAY = 0x01
        LOCATION_AND_NAVIGATION_DISPLAY = 0x02
        LOCATION_POD = 0x03
        LOCATION_AND_NAVIGATION_POD = 0x04

    class _OpenSubcategory(utils.OpenIntEnum):
        GENERIC = 0x00

    SUBCATEGORY_CLASSES = {
        Category.UNKNOWN: UnknownSubcategory,
        Category.PHONE: PhoneSubcategory,
        Category.COMPUTER: ComputerSubcategory,
        Category.WATCH: WatchSubcategory,
        Category.CLOCK: ClockSubcategory,
        Category.DISPLAY: DisplaySubcategory,
        Category.REMOTE_CONTROL: RemoteControlSubcategory,
        Category.EYE_GLASSES: EyeglassesSubcategory,
        Category.TAG: TagSubcategory,
        Category.KEYRING: KeyringSubcategory,
        Category.MEDIA_PLAYER: MediaPlayerSubcategory,
        Category.BARCODE_SCANNER: BarcodeScannerSubcategory,
        Category.THERMOMETER: ThermometerSubcategory,
        Category.HEART_RATE_SENSOR: HeartRateSensorSubcategory,
        Category.BLOOD_PRESSURE: BloodPressureSubcategory,
        Category.HUMAN_INTERFACE_DEVICE: HumanInterfaceDeviceSubcategory,
        Category.GLUCOSE_METER: GlucoseMeterSubcategory,
        Category.RUNNING_WALKING_SENSOR: RunningWalkingSensorSubcategory,
        Category.CYCLING: CyclingSubcategory,
        Category.CONTROL_DEVICE: ControlDeviceSubcategory,
        Category.NETWORK_DEVICE: NetworkDeviceSubcategory,
        Category.SENSOR: SensorSubcategory,
        Category.LIGHT_FIXTURES: LightFixturesSubcategory,
        Category.FAN: FanSubcategory,
        Category.HVAC: HvacSubcategory,
        Category.AIR_CONDITIONING: AirConditioningSubcategory,
        Category.HUMIDIFIER: HumidifierSubcategory,
        Category.HEATING: HeatingSubcategory,
        Category.ACCESS_CONTROL: AccessControlSubcategory,
        Category.MOTORIZED_DEVICE: MotorizedDeviceSubcategory,
        Category.POWER_DEVICE: PowerDeviceSubcategory,
        Category.LIGHT_SOURCE: LightSourceSubcategory,
        Category.WINDOW_COVERING: WindowCoveringSubcategory,
        Category.AUDIO_SINK: AudioSinkSubcategory,
        Category.AUDIO_SOURCE: AudioSourceSubcategory,
        Category.MOTORIZED_VEHICLE: MotorizedVehicleSubcategory,
        Category.DOMESTIC_APPLIANCE: DomesticApplianceSubcategory,
        Category.WEARABLE_AUDIO_DEVICE: WearableAudioDeviceSubcategory,
        Category.AIRCRAFT: AircraftSubcategory,
        Category.AV_EQUIPMENT: AvEquipmentSubcategory,
        Category.DISPLAY_EQUIPMENT: DisplayEquipmentSubcategory,
        Category.HEARING_AID: HearingAidSubcategory,
        Category.GAMING: GamingSubcategory,
        Category.SIGNAGE: SignageSubcategory,
        Category.PULSE_OXIMETER: PulseOximeterSubcategory,
        Category.WEIGHT_SCALE: WeightScaleSubcategory,
        Category.PERSONAL_MOBILITY_DEVICE: PersonalMobilityDeviceSubcategory,
        Category.CONTINUOUS_GLUCOSE_MONITOR: ContinuousGlucoseMonitorSubcategory,
        Category.INSULIN_PUMP: InsulinPumpSubcategory,
        Category.MEDICATION_DELIVERY: MedicationDeliverySubcategory,
        Category.SPIROMETER: SpirometerSubcategory,
        Category.OUTDOOR_SPORTS_ACTIVITY: OutdoorSportsActivitySubcategory,
    }

    category: Category
    subcategory: enum.IntEnum

    @classmethod
    def from_int(cls, appearance: int) -> Self:
        category = cls.Category(appearance >> 6)
        return cls(category, appearance & 0x3F)

    @classmethod
    def from_bytes(cls, data: bytes):
        return cls.from_int(int.from_bytes(data, byteorder="little"))

    def __init__(self, category: Category, subcategory: int) -> None:
        self.category = category
        if subcategory_class := self.SUBCATEGORY_CLASSES.get(category):
            self.subcategory = subcategory_class(subcategory)
        else:
            self.subcategory = self._OpenSubcategory(subcategory)

    def __int__(self) -> int:
        return self.category << 6 | self.subcategory

    def __bytes__(self) -> bytes:
        return int(self).to_bytes(2, byteorder="little")

    def __repr__(self) -> str:
        return (
            'Appearance('
            f'category={self.category.name}, '
            f'subcategory={self.subcategory.name}'
            ')'
        )

    def __str__(self) -> str:
        return f'{self.category.name}/{self.subcategory.name}'

    def __eq__(self, value: Any) -> bool:
        return (
            isinstance(value, Appearance)
            and self.category == value.category
            and self.subcategory == value.subcategory
        )


# -----------------------------------------------------------------------------
# Classes representing "Data Types" defined in
# "Supplement to the Bluetooth Core Specification", Part A
# -----------------------------------------------------------------------------
# TODO: use ABC, figure out multiple base classes with metaclasses
class DataType:
    # Human-reable label/name for the type
    label = ""

    # Advertising Data type ID for this data type.
    ad_type: AdvertisingData.Type = 0  # type: ignore

    def value_string(self) -> str:
        """Human-reable string representation of the value."""
        raise NotImplementedError()

    def to_string(self, use_label: bool = False) -> str:
        if use_label:
            return f"[{self.label}]: {self.value_string()}"

        return f"{self.__class__.__name__}({self.value_string()})"

    @classmethod
    def from_advertising_data(cls, advertising_data: AdvertisingData) -> Self | None:
        if (data := advertising_data.get(cls.ad_type, raw=True)) is None:
            return None

        return cls.from_bytes(data)

    @classmethod
    def all_from_advertising_data(cls, advertising_data: AdvertisingData) -> list[Self]:
        return [
            cls.from_bytes(data)
            for data in advertising_data.get_all(cls.ad_type, raw=True)
        ]

    @classmethod
    def from_bytes(cls, data: bytes) -> Self:
        """Create an instance from a serialized form."""
        raise NotImplementedError()

    def __bytes__(self) -> bytes:
        raise NotImplementedError()

    def __str__(self) -> str:
        return self.to_string()


# -----------------------------------------------------------------------------
# Advertising Data
# -----------------------------------------------------------------------------
AdvertisingDataObject = (
    list[UUID]
    | tuple[UUID, bytes]
    | bytes
    | str
    | int
    | tuple[int, int]
    | tuple[int, bytes]
    | Appearance
)


class AdvertisingData:
    # fmt: off
    # pylint: disable=line-too-long

    class Type(utils.OpenIntEnum):
        FLAGS                                               = 0x01
        INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS       = 0x02
        COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS         = 0x03
        INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS       = 0x04
        COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS         = 0x05
        INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS      = 0x06
        COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS        = 0x07
        SHORTENED_LOCAL_NAME                                = 0x08
        COMPLETE_LOCAL_NAME                                 = 0x09
        TX_POWER_LEVEL                                      = 0x0A
        CLASS_OF_DEVICE                                     = 0x0D
        SIMPLE_PAIRING_HASH_C                               = 0x0E
        SIMPLE_PAIRING_HASH_C_192                           = 0x0E
        SIMPLE_PAIRING_RANDOMIZER_R                         = 0x0F
        SIMPLE_PAIRING_RANDOMIZER_R_192                     = 0x0F
        DEVICE_ID                                           = 0x10
        SECURITY_MANAGER_TK_VALUE                           = 0x10
        SECURITY_MANAGER_OUT_OF_BAND_FLAGS                  = 0x11
        PERIPHERAL_CONNECTION_INTERVAL_RANGE                = 0x12
        LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS           = 0x14
        LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS          = 0x15
        SERVICE_DATA_16_BIT_UUID                            = 0x16
        PUBLIC_TARGET_ADDRESS                               = 0x17
        RANDOM_TARGET_ADDRESS                               = 0x18
        APPEARANCE                                          = 0x19
        ADVERTISING_INTERVAL                                = 0x1A
        LE_BLUETOOTH_DEVICE_ADDRESS                         = 0x1B
        LE_ROLE                                             = 0x1C
        SIMPLE_PAIRING_HASH_C_256                           = 0x1D
        SIMPLE_PAIRING_RANDOMIZER_R_256                     = 0x1E
        LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS           = 0x1F
        SERVICE_DATA_32_BIT_UUID                            = 0x20
        SERVICE_DATA_128_BIT_UUID                           = 0x21
        LE_SECURE_CONNECTIONS_CONFIRMATION_VALUE            = 0x22
        LE_SECURE_CONNECTIONS_RANDOM_VALUE                  = 0x23
        URI                                                 = 0x24
        INDOOR_POSITIONING                                  = 0x25
        TRANSPORT_DISCOVERY_DATA                            = 0x26
        LE_SUPPORTED_FEATURES                               = 0x27
        CHANNEL_MAP_UPDATE_INDICATION                       = 0x28
        PB_ADV                                              = 0x29
        MESH_MESSAGE                                        = 0x2A
        MESH_BEACON                                         = 0x2B
        BIGINFO                                             = 0x2C
        BROADCAST_CODE                                      = 0x2D
        RESOLVABLE_SET_IDENTIFIER                           = 0x2E
        ADVERTISING_INTERVAL_LONG                           = 0x2F
        BROADCAST_NAME                                      = 0x30
        ENCRYPTED_ADVERTISING_DATA                          = 0x31
        PERIODIC_ADVERTISING_RESPONSE_TIMING_INFORMATION    = 0x32
        ELECTRONIC_SHELF_LABEL                              = 0x34
        THREE_D_INFORMATION_DATA                            = 0x3D
        MANUFACTURER_SPECIFIC_DATA                          = 0xFF

    class Flags(utils.CompatibleIntFlag):
        LE_LIMITED_DISCOVERABLE_MODE = 1 << 0
        LE_GENERAL_DISCOVERABLE_MODE = 1 << 1
        BR_EDR_NOT_SUPPORTED = 1 << 2
        SIMULTANEOUS_LE_BR_EDR_CAPABLE = 1 << 3

    # For backward-compatibility
    FLAGS                                            = Type.FLAGS
    INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS    = Type.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS
    COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS      = Type.COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS
    INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS    = Type.INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS
    COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS      = Type.COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS
    INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS   = Type.INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS
    COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS     = Type.COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS
    SHORTENED_LOCAL_NAME                             = Type.SHORTENED_LOCAL_NAME
    COMPLETE_LOCAL_NAME                              = Type.COMPLETE_LOCAL_NAME
    TX_POWER_LEVEL                                   = Type.TX_POWER_LEVEL
    CLASS_OF_DEVICE                                  = Type.CLASS_OF_DEVICE
    SIMPLE_PAIRING_HASH_C                            = Type.SIMPLE_PAIRING_HASH_C
    SIMPLE_PAIRING_HASH_C_192                        = Type.SIMPLE_PAIRING_HASH_C_192
    SIMPLE_PAIRING_RANDOMIZER_R                      = Type.SIMPLE_PAIRING_RANDOMIZER_R
    SIMPLE_PAIRING_RANDOMIZER_R_192                  = Type.SIMPLE_PAIRING_RANDOMIZER_R_192
    DEVICE_ID                                        = Type.DEVICE_ID
    SECURITY_MANAGER_TK_VALUE                        = Type.SECURITY_MANAGER_TK_VALUE
    SECURITY_MANAGER_OUT_OF_BAND_FLAGS               = Type.SECURITY_MANAGER_OUT_OF_BAND_FLAGS
    PERIPHERAL_CONNECTION_INTERVAL_RANGE             = Type.PERIPHERAL_CONNECTION_INTERVAL_RANGE
    LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS        = Type.LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS
    LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS       = Type.LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS
    SERVICE_DATA                                     = Type.SERVICE_DATA_16_BIT_UUID
    SERVICE_DATA_16_BIT_UUID                         = Type.SERVICE_DATA_16_BIT_UUID
    PUBLIC_TARGET_ADDRESS                            = Type.PUBLIC_TARGET_ADDRESS
    RANDOM_TARGET_ADDRESS                            = Type.RANDOM_TARGET_ADDRESS
    APPEARANCE                                       = Type.APPEARANCE
    ADVERTISING_INTERVAL                             = Type.ADVERTISING_INTERVAL
    LE_BLUETOOTH_DEVICE_ADDRESS                      = Type.LE_BLUETOOTH_DEVICE_ADDRESS
    LE_ROLE                                          = Type.LE_ROLE
    SIMPLE_PAIRING_HASH_C_256                        = Type.SIMPLE_PAIRING_HASH_C_256
    SIMPLE_PAIRING_RANDOMIZER_R_256                  = Type.SIMPLE_PAIRING_RANDOMIZER_R_256
    LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS        = Type.LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS
    SERVICE_DATA_32_BIT_UUID                         = Type.SERVICE_DATA_32_BIT_UUID
    SERVICE_DATA_128_BIT_UUID                        = Type.SERVICE_DATA_128_BIT_UUID
    LE_SECURE_CONNECTIONS_CONFIRMATION_VALUE         = Type.LE_SECURE_CONNECTIONS_CONFIRMATION_VALUE
    LE_SECURE_CONNECTIONS_RANDOM_VALUE               = Type.LE_SECURE_CONNECTIONS_RANDOM_VALUE
    URI                                              = Type.URI
    INDOOR_POSITIONING                               = Type.INDOOR_POSITIONING
    TRANSPORT_DISCOVERY_DATA                         = Type.TRANSPORT_DISCOVERY_DATA
    LE_SUPPORTED_FEATURES                            = Type.LE_SUPPORTED_FEATURES
    CHANNEL_MAP_UPDATE_INDICATION                    = Type.CHANNEL_MAP_UPDATE_INDICATION
    PB_ADV                                           = Type.PB_ADV
    MESH_MESSAGE                                     = Type.MESH_MESSAGE
    MESH_BEACON                                      = Type.MESH_BEACON
    BIGINFO                                          = Type.BIGINFO
    BROADCAST_CODE                                   = Type.BROADCAST_CODE
    RESOLVABLE_SET_IDENTIFIER                        = Type.RESOLVABLE_SET_IDENTIFIER
    ADVERTISING_INTERVAL_LONG                        = Type.ADVERTISING_INTERVAL_LONG
    BROADCAST_NAME                                   = Type.BROADCAST_NAME
    ENCRYPTED_ADVERTISING_DATA                       = Type.ENCRYPTED_ADVERTISING_DATA
    PERIODIC_ADVERTISING_RESPONSE_TIMING_INFORMATION = Type.PERIODIC_ADVERTISING_RESPONSE_TIMING_INFORMATION
    ELECTRONIC_SHELF_LABEL                           = Type.ELECTRONIC_SHELF_LABEL
    THREE_D_INFORMATION_DATA                         = Type.THREE_D_INFORMATION_DATA
    MANUFACTURER_SPECIFIC_DATA                       = Type.MANUFACTURER_SPECIFIC_DATA

    LE_LIMITED_DISCOVERABLE_MODE_FLAG = Flags.LE_LIMITED_DISCOVERABLE_MODE
    LE_GENERAL_DISCOVERABLE_MODE_FLAG = Flags.LE_GENERAL_DISCOVERABLE_MODE
    BR_EDR_NOT_SUPPORTED_FLAG         = Flags.BR_EDR_NOT_SUPPORTED
    BR_EDR_CONTROLLER_FLAG            = Flags.SIMULTANEOUS_LE_BR_EDR_CAPABLE
    BR_EDR_HOST_FLAG                  = 0x10 # Deprecated

    ad_structures: list[tuple[AdvertisingData.Type, bytes]]

    # fmt: on
    # pylint: enable=line-too-long

    def __init__(
        self,
        ad_structures: Iterable[tuple[int, bytes] | DataType] | None = None,
    ) -> None:
        if ad_structures is None:
            ad_structures = []
        self.ad_structures = [
            (
                (element.ad_type, bytes(element))
                if isinstance(element, DataType)
                else (AdvertisingData.Type(element[0]), element[1])
            )
            for element in ad_structures
        ]

    @classmethod
    def from_bytes(cls, data: bytes) -> AdvertisingData:
        instance = AdvertisingData()
        instance.append(data)
        return instance

    @staticmethod
    def flags_to_string(flags, short=False):
        flag_names = (
            ['LE Limited', 'LE General', 'No BR/EDR', 'BR/EDR C', 'BR/EDR H']
            if short
            else [
                'LE Limited Discoverable Mode',
                'LE General Discoverable Mode',
                'BR/EDR Not Supported',
                'Simultaneous LE and BR/EDR',
            ]
        )
        return ', '.join(bit_flags_to_strings(flags, flag_names))

    @staticmethod
    def uuid_list_to_objects(ad_data: bytes, uuid_size: int) -> list[UUID]:
        uuids = []
        offset = 0
        while (offset + uuid_size) <= len(ad_data):
            uuids.append(UUID.from_bytes(ad_data[offset : offset + uuid_size]))
            offset += uuid_size
        return uuids

    @staticmethod
    def uuid_list_to_string(ad_data, uuid_size):
        return ', '.join(
            [
                str(uuid)
                for uuid in AdvertisingData.uuid_list_to_objects(ad_data, uuid_size)
            ]
        )

    @classmethod
    def ad_data_to_string(cls, ad_type: int, ad_data: bytes) -> str:
        match ad_type:
            case AdvertisingData.FLAGS:
                ad_type_str = 'Flags'
                ad_data_str = AdvertisingData.flags_to_string(ad_data[0], short=True)
            case AdvertisingData.COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS:
                ad_type_str = 'Complete List of 16-bit Service Class UUIDs'
                ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 2)
            case AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS:
                ad_type_str = 'Incomplete List of 16-bit Service Class UUIDs'
                ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 2)
            case AdvertisingData.COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS:
                ad_type_str = 'Complete List of 32-bit Service Class UUIDs'
                ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 4)
            case AdvertisingData.INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS:
                ad_type_str = 'Incomplete List of 32-bit Service Class UUIDs'
                ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 4)
            case AdvertisingData.COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS:
                ad_type_str = 'Complete List of 128-bit Service Class UUIDs'
                ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 16)
            case AdvertisingData.INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS:
                ad_type_str = 'Incomplete List of 128-bit Service Class UUIDs'
                ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 16)
            case AdvertisingData.SERVICE_DATA_16_BIT_UUID:
                ad_type_str = 'Service Data'
                uuid = UUID.from_bytes(ad_data[:2])
                ad_data_str = f'service={uuid}, data={ad_data[2:].hex()}'
            case AdvertisingData.SERVICE_DATA_32_BIT_UUID:
                ad_type_str = 'Service Data'
                uuid = UUID.from_bytes(ad_data[:4])
                ad_data_str = f'service={uuid}, data={ad_data[4:].hex()}'
            case AdvertisingData.SERVICE_DATA_128_BIT_UUID:
                ad_type_str = 'Service Data'
                uuid = UUID.from_bytes(ad_data[:16])
                ad_data_str = f'service={uuid}, data={ad_data[16:].hex()}'
            case AdvertisingData.SHORTENED_LOCAL_NAME:
                ad_type_str = 'Shortened Local Name'
                ad_data_str = f'"{ad_data.decode("utf-8")}"'
            case AdvertisingData.COMPLETE_LOCAL_NAME:
                ad_type_str = 'Complete Local Name'
                try:
                    ad_data_str = f'"{ad_data.decode("utf-8")}"'
                except UnicodeDecodeError:
                    ad_data_str = ad_data.hex()
            case AdvertisingData.TX_POWER_LEVEL:
                ad_type_str = 'TX Power Level'
                ad_data_str = str(ad_data[0])
            case AdvertisingData.MANUFACTURER_SPECIFIC_DATA:
                ad_type_str = 'Manufacturer Specific Data'
                company_id = struct.unpack_from('<H', ad_data, 0)[0]
                company_name = COMPANY_IDENTIFIERS.get(
                    company_id, f'0x{company_id:04X}'
                )
                ad_data_str = f'company={company_name}, data={ad_data[2:].hex()}'
            case AdvertisingData.APPEARANCE:
                ad_type_str = 'Appearance'
                appearance = Appearance.from_int(
                    struct.unpack_from('<H', ad_data, 0)[0]
                )
                ad_data_str = str(appearance)
            case AdvertisingData.BROADCAST_NAME:
                ad_type_str = 'Broadcast Name'
                ad_data_str = ad_data.decode('utf-8')
            case _:
                ad_type_str = AdvertisingData.Type(ad_type).name
                ad_data_str = ad_data.hex()

        return f'[{ad_type_str}]: {ad_data_str}'

    # pylint: disable=too-many-return-statements
    @classmethod
    def ad_data_to_object(cls, ad_type: int, ad_data: bytes) -> AdvertisingDataObject:
        if ad_type in (
            AdvertisingData.Type.COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS,
        ):
            return AdvertisingData.uuid_list_to_objects(ad_data, 2)

        if ad_type in (
            AdvertisingData.Type.COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS,
        ):
            return AdvertisingData.uuid_list_to_objects(ad_data, 4)

        if ad_type in (
            AdvertisingData.Type.COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS,
        ):
            return AdvertisingData.uuid_list_to_objects(ad_data, 16)

        if ad_type == AdvertisingData.Type.SERVICE_DATA_16_BIT_UUID:
            return (UUID.from_bytes(ad_data[:2]), ad_data[2:])

        if ad_type == AdvertisingData.Type.SERVICE_DATA_32_BIT_UUID:
            return (UUID.from_bytes(ad_data[:4]), ad_data[4:])

        if ad_type == AdvertisingData.Type.SERVICE_DATA_128_BIT_UUID:
            return (UUID.from_bytes(ad_data[:16]), ad_data[16:])

        if ad_type in (
            AdvertisingData.Type.SHORTENED_LOCAL_NAME,
            AdvertisingData.Type.COMPLETE_LOCAL_NAME,
            AdvertisingData.Type.URI,
            AdvertisingData.Type.BROADCAST_NAME,
        ):
            return ad_data.decode("utf-8")

        if ad_type in (AdvertisingData.Type.TX_POWER_LEVEL, AdvertisingData.Type.FLAGS):
            return cast(int, struct.unpack('B', ad_data)[0])

        if ad_type in (AdvertisingData.Type.ADVERTISING_INTERVAL,):
            return cast(int, struct.unpack('<H', ad_data)[0])

        if ad_type == AdvertisingData.Type.CLASS_OF_DEVICE:
            return cast(int, struct.unpack('<I', bytes([*ad_data, 0]))[0])

        if ad_type == AdvertisingData.Type.PERIPHERAL_CONNECTION_INTERVAL_RANGE:
            return cast(tuple[int, int], struct.unpack('<HH', ad_data))

        if ad_type == AdvertisingData.Type.APPEARANCE:
            return Appearance.from_int(
                cast(int, struct.unpack_from('<H', ad_data, 0)[0])
            )

        if ad_type == AdvertisingData.Type.MANUFACTURER_SPECIFIC_DATA:
            return (cast(int, struct.unpack_from('<H', ad_data, 0)[0]), ad_data[2:])

        return ad_data

    def append(self, data: bytes) -> None:
        offset = 0
        while offset + 1 < len(data):
            length = data[offset]
            offset += 1
            if length > 0:
                ad_type = data[offset]
                ad_data = data[offset + 1 : offset + length]
                self.ad_structures.append((AdvertisingData.Type(ad_type), ad_data))
            offset += length

    @overload
    def get_all(
        self,
        type_id: Literal[
            AdvertisingData.Type.COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS,
            AdvertisingData.Type.COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS,
            AdvertisingData.Type.COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS,
        ],
        raw: Literal[False] = False,
    ) -> list[list[UUID]]: ...

    @overload
    def get_all(
        self,
        type_id: Literal[
            AdvertisingData.Type.SERVICE_DATA_16_BIT_UUID,
            AdvertisingData.Type.SERVICE_DATA_32_BIT_UUID,
            AdvertisingData.Type.SERVICE_DATA_128_BIT_UUID,
        ],
        raw: Literal[False] = False,
    ) -> list[tuple[UUID, bytes]]: ...

    @overload
    def get_all(
        self,
        type_id: Literal[
            AdvertisingData.Type.SHORTENED_LOCAL_NAME,
            AdvertisingData.Type.COMPLETE_LOCAL_NAME,
            AdvertisingData.Type.URI,
            AdvertisingData.Type.BROADCAST_NAME,
        ],
        raw: Literal[False] = False,
    ) -> list[str]: ...

    @overload
    def get_all(
        self,
        type_id: Literal[
            AdvertisingData.Type.TX_POWER_LEVEL,
            AdvertisingData.Type.FLAGS,
            AdvertisingData.Type.ADVERTISING_INTERVAL,
            AdvertisingData.Type.CLASS_OF_DEVICE,
        ],
        raw: Literal[False] = False,
    ) -> list[int]: ...

    @overload
    def get_all(
        self,
        type_id: Literal[AdvertisingData.Type.PERIPHERAL_CONNECTION_INTERVAL_RANGE,],
        raw: Literal[False] = False,
    ) -> list[tuple[int, int]]: ...

    @overload
    def get_all(
        self,
        type_id: Literal[AdvertisingData.Type.MANUFACTURER_SPECIFIC_DATA,],
        raw: Literal[False] = False,
    ) -> list[tuple[int, bytes]]: ...

    @overload
    def get_all(
        self,
        type_id: Literal[AdvertisingData.Type.APPEARANCE,],
        raw: Literal[False] = False,
    ) -> list[Appearance]: ...

    @overload
    def get_all(self, type_id: int, raw: Literal[True]) -> list[bytes]: ...

    @overload
    def get_all(
        self, type_id: int, raw: bool = False
    ) -> list[AdvertisingDataObject]: ...

    def get_all(self, type_id: int, raw: bool = False) -> list[AdvertisingDataObject]:  # type: ignore[misc]
        '''
        Get all advertising data elements as simple AdvertisingDataObject objects.

        Returns a (possibly empty) list of matches.
        '''

        def process_ad_data(ad_data: bytes) -> AdvertisingDataObject:
            return ad_data if raw else self.ad_data_to_object(type_id, ad_data)

        return [process_ad_data(ad[1]) for ad in self.ad_structures if ad[0] == type_id]

    @overload
    def get(
        self,
        type_id: Literal[
            AdvertisingData.Type.COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS,
            AdvertisingData.Type.COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS,
            AdvertisingData.Type.COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS,
            AdvertisingData.Type.LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS,
        ],
        raw: Literal[False] = False,
    ) -> list[UUID] | None: ...

    @overload
    def get(
        self,
        type_id: Literal[
            AdvertisingData.Type.SERVICE_DATA_16_BIT_UUID,
            AdvertisingData.Type.SERVICE_DATA_32_BIT_UUID,
            AdvertisingData.Type.SERVICE_DATA_128_BIT_UUID,
        ],
        raw: Literal[False] = False,
    ) -> tuple[UUID, bytes] | None: ...

    @overload
    def get(
        self,
        type_id: Literal[
            AdvertisingData.Type.SHORTENED_LOCAL_NAME,
            AdvertisingData.Type.COMPLETE_LOCAL_NAME,
            AdvertisingData.Type.URI,
            AdvertisingData.Type.BROADCAST_NAME,
        ],
        raw: Literal[False] = False,
    ) -> str | None: ...

    @overload
    def get(
        self,
        type_id: Literal[
            AdvertisingData.Type.TX_POWER_LEVEL,
            AdvertisingData.Type.FLAGS,
            AdvertisingData.Type.ADVERTISING_INTERVAL,
            AdvertisingData.Type.CLASS_OF_DEVICE,
        ],
        raw: Literal[False] = False,
    ) -> int | None: ...

    @overload
    def get(
        self,
        type_id: Literal[AdvertisingData.Type.PERIPHERAL_CONNECTION_INTERVAL_RANGE,],
        raw: Literal[False] = False,
    ) -> tuple[int, int] | None: ...

    @overload
    def get(
        self,
        type_id: Literal[AdvertisingData.Type.MANUFACTURER_SPECIFIC_DATA,],
        raw: Literal[False] = False,
    ) -> tuple[int, bytes] | None: ...

    @overload
    def get(
        self,
        type_id: Literal[AdvertisingData.Type.APPEARANCE,],
        raw: Literal[False] = False,
    ) -> Appearance | None: ...

    @overload
    def get(self, type_id: int, raw: Literal[True]) -> bytes | None: ...

    @overload
    def get(self, type_id: int, raw: bool = False) -> AdvertisingDataObject | None: ...

    def get(self, type_id: int, raw: bool = False) -> AdvertisingDataObject | None:
        '''
        Get advertising data as a simple AdvertisingDataObject object.

        Returns the first entry, or None if no structure matches.
        '''

        all_objects = self.get_all(type_id, raw=raw)
        return all_objects[0] if all_objects else None

    def __bytes__(self):
        return b''.join(
            [bytes([len(x[1]) + 1, x[0]]) + x[1] for x in self.ad_structures]
        )

    def to_string(self, separator=', '):
        return separator.join(
            [AdvertisingData.ad_data_to_string(x[0], x[1]) for x in self.ad_structures]
        )

    def __str__(self):
        return self.to_string()


# -----------------------------------------------------------------------------
# Connection PHY
# -----------------------------------------------------------------------------
@dataclasses.dataclass
class ConnectionPHY:
    tx_phy: int
    rx_phy: int


# -----------------------------------------------------------------------------
# LE Role
# -----------------------------------------------------------------------------
class LeRole(enum.IntEnum):
    # fmt: off
    PERIPHERAL_ONLY           = 0x00
    CENTRAL_ONLY              = 0x01
    BOTH_PERIPHERAL_PREFERRED = 0x02
    BOTH_CENTRAL_PREFERRED    = 0x03


# -----------------------------------------------------------------------------
# Security Manager OOB Flag
# -----------------------------------------------------------------------------
class SecurityManagerOutOfBandFlag(utils.CompatibleIntFlag):
    """
    See Supplement to the Bluetooth Core Specification, Part A
    1.7 SECURITY MANAGER OUT OF BAND (OOB)
    """

    OOB_FLAGS_FIELD = 1 << 0
    LE_SUPPORTED = 1 << 1
    ADDRESS_TYPE = 1 << 3
