from dataclasses import dataclass, field, fields
from enum import IntEnum, StrEnum
from typing import Any, Self

from roborock.data.code_mappings import RoborockProductNickname
from roborock.data.containers import RoborockBase
from roborock.data.v1 import RoborockDockTypeCode


class NewFeatureStrBit(IntEnum):
    TWO_KEY_REAL_TIME_VIDEO = 32
    TWO_KEY_RTV_IN_CHARGING = 33
    DIRTY_REPLENISH_CLEAN = 34
    AUTO_DELIVERY_FIELD_IN_GLOBAL_STATUS = 35
    AVOID_COLLISION_MODE = 36
    VOICE_CONTROL = 37
    NEW_ENDPOINT = 38
    PUMPING_WATER = 39
    CORNER_MOP_STRETCH = 40
    HOT_WASH_TOWEL = 41
    FLOOR_DIR_CLEAN_ANY_TIME = 42
    PET_SUPPLIES_DEEP_CLEAN = 43
    MOP_SHAKE_WATER_MAX = 45
    EXACT_CUSTOM_MODE = 47
    VIDEO_PATROL = 48
    CARPET_CUSTOM_CLEAN = 49
    PET_SNAPSHOT = 50
    CUSTOM_CLEAN_MODE_COUNT = 51
    NEW_AI_RECOGNITION = 52
    AUTO_COLLECTION_2 = 53
    RIGHT_BRUSH_STRETCH = 54
    SMART_CLEAN_MODE_SET = 55
    DIRTY_OBJECT_DETECT = 56
    NO_NEED_CARPET_PRESS_SET = 57
    VOICE_CONTROL_LED = 58
    WATER_LEAK_CHECK = 60
    MIN_BATTERY_15_TO_CLEAN_TASK = 62
    GAP_DEEP_CLEAN = 63
    OBJECT_DETECT_CHECK = 64
    IDENTIFY_ROOM = 66
    MATTER = 67
    WORKDAY_HOLIDAY = 69
    CLEAN_DIRECT_STATUS = 70
    MAP_ERASER = 71
    OPTIMIZE_BATTERY = 72
    ACTIVATE_VIDEO_CHARGING_AND_STANDBY = 73
    CARPET_LONG_HAIRED = 75
    CLEAN_HISTORY_TIME_LINE = 76
    MAX_ZONE_OPENED = 77
    EXHIBITION_FUNCTION = 78
    LDS_LIFTING = 79
    AUTO_TEAR_DOWN_MOP = 80
    SMALL_SIDE_MOP = 81
    SUPPORT_SIDE_BRUSH_UP_DOWN = 82
    DRY_INTERVAL_TIMER = 83
    UVC_STERILIZE = 84
    MIDWAY_BACK_TO_DOCK = 85
    SUPPORT_MAIN_BRUSH_UP_DOWN = 86
    EGG_DANCE_MODE = 87
    MECHANICAL_ARM_MODE = 89
    TIDYUP_ZONES = MECHANICAL_ARM_MODE
    CLEAN_TIME_LINE = 91
    CLEAN_THEN_MOP_MODE = 93
    TYPE_IDENTIFY = 94
    SUPPORT_GET_PARTICULAR_STATUS = 96
    THREE_D_MAPPING_INNER_TEST = 97
    SYNC_SERVER_NAME = 98
    SHOULD_SHOW_ARM_OVER_LOAD = 99
    COLLECT_DUST_COUNT_SHOW = 100
    SUPPORT_API_APP_STOP_GRASP = 101
    CTM_WITH_REPEAT = 102
    SIDE_BRUSH_LIFT_CARPET = 104
    DETECT_WIRE_CARPET = 105
    WATER_SLIDE_MODE = 106
    SOAK_AND_WASH = 107
    CLEAN_EFFICIENCY = 108
    BACK_WASH_NEW_SMART = 109
    DUAL_BAND_WI_FI = 110
    PROGRAM_MODE = 111
    CLEAN_FLUID_DELIVERY = 112
    CARPET_LONG_HAIRED_EX = 113
    OVER_SEA_CTM = 114
    FULL_DUPLES_SWITCH = 115
    LOW_AREA_ACCESS = 116
    FOLLOW_LOW_OBS = 117
    TWO_GEARS_NO_COLLISION = 118
    CARPET_SHAPE_TYPE = 119
    SR_MAP = 120


class ProductFeatures(StrEnum):
    REMOTE_BACK = "remote_back"
    CLEANMODE_MAXPLUS = "cleanmode_maxplus"
    CLEANMODE_PURECLEANMOP = "cleanmode_purecleanmop"
    CLEANMODE_NONE_PURECLEANMOP_WITH_MAXPLUS = "cleanmode_none_purecleanmop_with_maxplus"
    MOP_ELECTRONIC_MODULE = "mop_electronic_module"
    MOP_SHAKE_MODULE = "mop_shake_module"
    MOP_SPIN_MODULE = "mop_spin_module"
    DEFAULT_MAP3D = "map3d"
    DEFAULT_CLEANMODECUSTOM = "custom_cleanmode"
    REALTIMEVIDEO = "realtimevideo"
    REALTIMEVIDEO_LIVECALL = "realtimevideo_livecall"
    REALTIMEVIDEO_RECORDANDSHORTCUT = "realtimevideo_livecall"
    CAMERA_SINGLELINE = "camera_singleline"
    CAMERA_DUALLINE = "camera_dualline"
    CAMERA_RGB = "camera_rgb"
    CAMERA_DOUBLERGB = "camera_doublergb"
    AIRECOGNITION_SETTING = "airecognition_setting"
    AIRECOGNITION_SCENE = "airecognition_scene"
    AIRECOGNITION_PET = "airecognition_pet"
    AIRECOGNITION_OBSTACLE = "airecognition_obstacle"


# The following combinations are pulled directly from decompiled source code.
AIRECOGNITION_OBSTACLE = [ProductFeatures.AIRECOGNITION_OBSTACLE]
RGB_CAMERA_FEATURES = [
    ProductFeatures.CAMERA_RGB,
    ProductFeatures.AIRECOGNITION_SETTING,
    ProductFeatures.AIRECOGNITION_SCENE,
    ProductFeatures.AIRECOGNITION_PET,
    ProductFeatures.AIRECOGNITION_OBSTACLE,
    ProductFeatures.REALTIMEVIDEO,
    ProductFeatures.REALTIMEVIDEO_LIVECALL,
    ProductFeatures.REALTIMEVIDEO_RECORDANDSHORTCUT,
]
DOUBLE_RGB_CAMERA_FEATURES = [
    ProductFeatures.CAMERA_DOUBLERGB,
    ProductFeatures.AIRECOGNITION_SETTING,
    ProductFeatures.AIRECOGNITION_PET,
    ProductFeatures.AIRECOGNITION_OBSTACLE,
    ProductFeatures.REALTIMEVIDEO,
]
SINGLE_LINE_CAMERA_FEATURES = [
    ProductFeatures.CAMERA_SINGLELINE,
    ProductFeatures.AIRECOGNITION_SETTING,
    ProductFeatures.AIRECOGNITION_OBSTACLE,
]
DUAL_LINE_CAMERA_FEATURES = [
    ProductFeatures.CAMERA_DUALLINE,
    ProductFeatures.AIRECOGNITION_SETTING,
    ProductFeatures.AIRECOGNITION_OBSTACLE,
    ProductFeatures.AIRECOGNITION_PET,
]

NEW_DEFAULT_FEATURES = [ProductFeatures.REMOTE_BACK, ProductFeatures.CLEANMODE_MAXPLUS]


PEARL_FEATURES = SINGLE_LINE_CAMERA_FEATURES + [ProductFeatures.CLEANMODE_MAXPLUS, ProductFeatures.MOP_SPIN_MODULE]
PEARL_PLUS_FEATURES = NEW_DEFAULT_FEATURES + RGB_CAMERA_FEATURES + [ProductFeatures.MOP_SPIN_MODULE]
ULTRON_FEATURES = NEW_DEFAULT_FEATURES + DUAL_LINE_CAMERA_FEATURES + [ProductFeatures.MOP_SHAKE_MODULE]
ULTRONSV_FEATURES = NEW_DEFAULT_FEATURES + RGB_CAMERA_FEATURES + [ProductFeatures.MOP_SHAKE_MODULE]
TANOSS_FEATURES = [ProductFeatures.REMOTE_BACK, ProductFeatures.MOP_SHAKE_MODULE]
TOPAZSPOWER_FEATURES = [ProductFeatures.CLEANMODE_MAXPLUS, ProductFeatures.MOP_SHAKE_MODULE]

PRODUCTS_WITHOUT_CUSTOM_CLEAN: set[RoborockProductNickname] = {
    RoborockProductNickname.TANOS,
    RoborockProductNickname.RUBYPLUS,
    RoborockProductNickname.RUBYSC,
    RoborockProductNickname.RUBYSE,
}
PRODUCTS_WITHOUT_DEFAULT_3D_MAP: set[RoborockProductNickname] = {
    RoborockProductNickname.TANOS,
    RoborockProductNickname.TANOSSPLUS,
    RoborockProductNickname.TANOSE,
    RoborockProductNickname.TANOSV,
    RoborockProductNickname.RUBYPLUS,
    RoborockProductNickname.RUBYSC,
    RoborockProductNickname.RUBYSE,
}
PRODUCTS_WITHOUT_PURE_CLEAN_MOP: set[RoborockProductNickname] = {
    RoborockProductNickname.TANOS,
    RoborockProductNickname.TANOSE,
    RoborockProductNickname.TANOSV,
    RoborockProductNickname.TANOSSLITE,
    RoborockProductNickname.TANOSSE,
    RoborockProductNickname.TANOSSC,
    RoborockProductNickname.ULTRONLITE,
    RoborockProductNickname.ULTRONE,
    RoborockProductNickname.RUBYPLUS,
    RoborockProductNickname.RUBYSLITE,
    RoborockProductNickname.RUBYSC,
    RoborockProductNickname.RUBYSE,
}

# Base map containing the initial, unconditional features for each product.
_BASE_PRODUCT_FEATURE_MAP: dict[RoborockProductNickname, list[ProductFeatures]] = {
    RoborockProductNickname.PEARL: PEARL_FEATURES,
    RoborockProductNickname.PEARLS: PEARL_FEATURES,
    RoborockProductNickname.PEARLPLUS: PEARL_PLUS_FEATURES,
    RoborockProductNickname.VIVIAN: PEARL_PLUS_FEATURES,
    RoborockProductNickname.CORAL: PEARL_PLUS_FEATURES,
    RoborockProductNickname.ULTRON: ULTRON_FEATURES,
    RoborockProductNickname.ULTRONE: [ProductFeatures.CLEANMODE_NONE_PURECLEANMOP_WITH_MAXPLUS],
    RoborockProductNickname.ULTRONSV: ULTRONSV_FEATURES,
    RoborockProductNickname.TOPAZSPOWER: TOPAZSPOWER_FEATURES,
    RoborockProductNickname.TANOSS: TANOSS_FEATURES,
    RoborockProductNickname.PEARLC: PEARL_FEATURES,
    RoborockProductNickname.PEARLPLUSS: PEARL_PLUS_FEATURES,
    RoborockProductNickname.PEARLSLITE: PEARL_FEATURES,
    RoborockProductNickname.PEARLE: PEARL_FEATURES,
    RoborockProductNickname.PEARLELITE: PEARL_FEATURES,
    RoborockProductNickname.VIVIANC: [ProductFeatures.CLEANMODE_MAXPLUS, ProductFeatures.MOP_SPIN_MODULE]
    + SINGLE_LINE_CAMERA_FEATURES,
    RoborockProductNickname.CORALPRO: PEARL_PLUS_FEATURES,
    RoborockProductNickname.ULTRONLITE: SINGLE_LINE_CAMERA_FEATURES
    + [ProductFeatures.CLEANMODE_NONE_PURECLEANMOP_WITH_MAXPLUS, ProductFeatures.MOP_ELECTRONIC_MODULE],
    RoborockProductNickname.ULTRONSC: ULTRON_FEATURES,
    RoborockProductNickname.ULTRONSE: [
        ProductFeatures.CLEANMODE_NONE_PURECLEANMOP_WITH_MAXPLUS,
        ProductFeatures.MOP_ELECTRONIC_MODULE,
    ],
    RoborockProductNickname.ULTRONSPLUS: ULTRON_FEATURES,
    RoborockProductNickname.VERDELITE: ULTRONSV_FEATURES,
    RoborockProductNickname.TOPAZS: [ProductFeatures.REMOTE_BACK, ProductFeatures.MOP_SHAKE_MODULE],
    RoborockProductNickname.TOPAZSPLUS: NEW_DEFAULT_FEATURES
    + DUAL_LINE_CAMERA_FEATURES
    + [ProductFeatures.MOP_SHAKE_MODULE],
    RoborockProductNickname.TOPAZSC: TOPAZSPOWER_FEATURES + SINGLE_LINE_CAMERA_FEATURES,
    RoborockProductNickname.TOPAZSV: NEW_DEFAULT_FEATURES + RGB_CAMERA_FEATURES + [ProductFeatures.MOP_SHAKE_MODULE],
    RoborockProductNickname.TANOSSPLUS: TANOSS_FEATURES + DUAL_LINE_CAMERA_FEATURES,
    RoborockProductNickname.TANOSSLITE: [ProductFeatures.MOP_ELECTRONIC_MODULE],
    RoborockProductNickname.TANOSSC: [],
    RoborockProductNickname.TANOSSE: [],
    RoborockProductNickname.TANOSSMAX: NEW_DEFAULT_FEATURES
    + DUAL_LINE_CAMERA_FEATURES
    + [ProductFeatures.MOP_SHAKE_MODULE],
    RoborockProductNickname.TANOS: [ProductFeatures.REMOTE_BACK],
    RoborockProductNickname.TANOSE: [ProductFeatures.MOP_ELECTRONIC_MODULE, ProductFeatures.REMOTE_BACK],
    RoborockProductNickname.TANOSV: DOUBLE_RGB_CAMERA_FEATURES
    + [ProductFeatures.REMOTE_BACK, ProductFeatures.MOP_ELECTRONIC_MODULE],
    RoborockProductNickname.RUBYPLUS: [],
    RoborockProductNickname.RUBYSC: [],
    RoborockProductNickname.RUBYSE: [],
    RoborockProductNickname.RUBYSLITE: [ProductFeatures.MOP_ELECTRONIC_MODULE],
}

PRODUCT_FEATURE_MAP: dict[RoborockProductNickname, list[ProductFeatures]] = {
    product: (
        features
        + ([ProductFeatures.DEFAULT_CLEANMODECUSTOM] if product not in PRODUCTS_WITHOUT_CUSTOM_CLEAN else [])
        + ([ProductFeatures.DEFAULT_MAP3D] if product not in PRODUCTS_WITHOUT_DEFAULT_3D_MAP else [])
        + ([ProductFeatures.CLEANMODE_PURECLEANMOP] if product not in PRODUCTS_WITHOUT_PURE_CLEAN_MOP else [])
    )
    for product, features in _BASE_PRODUCT_FEATURE_MAP.items()
}


@dataclass
class DeviceFeatures(RoborockBase):
    """Represents the features supported by a Roborock device."""

    # Features from robot_new_features (lower 32 bits)
    is_show_clean_finish_reason_supported: bool = field(metadata={"robot_new_features": 1})
    is_re_segment_supported: bool = field(metadata={"robot_new_features": 4})
    is_video_monitor_supported: bool = field(metadata={"robot_new_features": 8})
    is_any_state_transit_goto_supported: bool = field(metadata={"robot_new_features": 16})
    is_fw_filter_obstacle_supported: bool = field(metadata={"robot_new_features": 32})
    is_video_setting_supported: bool = field(metadata={"robot_new_features": 64})
    is_ignore_unknown_map_object_supported: bool = field(metadata={"robot_new_features": 128})
    is_set_child_supported: bool = field(metadata={"robot_new_features": 256})
    is_carpet_supported: bool = field(metadata={"robot_new_features": 512})
    is_record_allowed: bool = field(metadata={"robot_new_features": 1024})
    is_mop_path_supported: bool = field(metadata={"robot_new_features": 2048})
    is_multi_map_segment_timer_supported: bool = field(metadata={"robot_new_features": 4096})
    is_current_map_restore_enabled: bool = field(metadata={"robot_new_features": 8192})
    is_room_name_supported: bool = field(metadata={"robot_new_features": 16384})
    is_shake_mop_set_supported: bool = field(metadata={"robot_new_features": 262144})
    is_map_beautify_internal_debug_supported: bool = field(metadata={"robot_new_features": 2097152})
    is_new_data_for_clean_history: bool = field(metadata={"robot_new_features": 4194304})
    is_new_data_for_clean_history_detail: bool = field(metadata={"robot_new_features": 8388608})
    is_flow_led_setting_supported: bool = field(metadata={"robot_new_features": 16777216})
    is_dust_collection_setting_supported: bool = field(metadata={"robot_new_features": 33554432})
    is_rpc_retry_supported: bool = field(metadata={"robot_new_features": 67108864})
    is_avoid_collision_supported: bool = field(metadata={"robot_new_features": 134217728})
    is_support_set_switch_map_mode: bool = field(metadata={"robot_new_features": 268435456})
    is_map_carpet_add_support: bool = field(metadata={"robot_new_features": 1073741824})
    is_custom_water_box_distance_supported: bool = field(metadata={"robot_new_features": 2147483648})

    # Features from robot_new_features (upper 32 bits)
    is_support_smart_scene: bool = field(metadata={"upper_32_bits": 1})
    is_support_floor_edit: bool = field(metadata={"upper_32_bits": 3})
    is_support_furniture: bool = field(metadata={"upper_32_bits": 4})
    is_wash_then_charge_cmd_supported: bool = field(metadata={"upper_32_bits": 5})
    is_support_room_tag: bool = field(metadata={"upper_32_bits": 6})
    is_support_quick_map_builder: bool = field(metadata={"upper_32_bits": 7})
    is_support_smart_global_clean_with_custom_mode: bool = field(metadata={"upper_32_bits": 8})
    is_careful_slow_mop_supported: bool = field(metadata={"upper_32_bits": 9})
    is_egg_mode_supported_from_new_features: bool = field(metadata={"upper_32_bits": 10})
    is_carpet_show_on_map: bool = field(metadata={"upper_32_bits": 12})
    is_supported_valley_electricity: bool = field(metadata={"upper_32_bits": 13})
    is_unsave_map_reason_supported: bool = field(metadata={"upper_32_bits": 14})
    is_supported_drying: bool = field(metadata={"upper_32_bits": 15})
    is_supported_download_test_voice: bool = field(metadata={"upper_32_bits": 16})
    is_support_backup_map: bool = field(metadata={"upper_32_bits": 17})
    is_support_custom_mode_in_cleaning: bool = field(metadata={"upper_32_bits": 18})
    is_support_remote_control_in_call: bool = field(metadata={"upper_32_bits": 19})

    # Features from new_feature_info_str (masking last 8 chars / 32 bits)
    is_support_set_volume_in_call: bool = field(metadata={"new_feature_str_mask": (1, 8)})
    is_support_clean_estimate: bool = field(metadata={"new_feature_str_mask": (2, 8)})
    is_support_custom_dnd: bool = field(metadata={"new_feature_str_mask": (4, 8)})
    is_carpet_deep_clean_supported: bool = field(metadata={"new_feature_str_mask": (8, 8)})
    is_support_stuck_zone: bool = field(metadata={"new_feature_str_mask": (16, 8)})
    is_support_custom_door_sill: bool = field(metadata={"new_feature_str_mask": (32, 8)})
    is_wifi_manage_supported: bool = field(metadata={"new_feature_str_mask": (128, 8)})
    is_clean_route_fast_mode_supported: bool = field(metadata={"new_feature_str_mask": (256, 8)})
    is_support_cliff_zone: bool = field(metadata={"new_feature_str_mask": (512, 8)})
    is_support_smart_door_sill: bool = field(metadata={"new_feature_str_mask": (1024, 8)})
    is_support_floor_direction: bool = field(metadata={"new_feature_str_mask": (2048, 8)})
    is_back_charge_auto_wash_supported: bool = field(metadata={"new_feature_str_mask": (4096, 8)})
    is_support_incremental_map: bool = field(metadata={"new_feature_str_mask": (4194304, 8)})
    is_offline_map_supported: bool = field(metadata={"new_feature_str_mask": (16384, 8)})
    is_super_deep_wash_supported: bool = field(metadata={"new_feature_str_mask": (32768, 8)})
    is_ces_2022_supported: bool = field(metadata={"new_feature_str_mask": (65536, 8)})
    is_dss_believable: bool = field(metadata={"new_feature_str_mask": (131072, 8)})
    is_main_brush_up_down_supported_from_str: bool = field(metadata={"new_feature_str_mask": (262144, 8)})
    is_goto_pure_clean_path_supported: bool = field(metadata={"new_feature_str_mask": (524288, 8)})
    is_water_up_down_drain_supported: bool = field(metadata={"new_feature_str_mask": (1048576, 8)})
    is_setting_carpet_first_supported: bool = field(metadata={"new_feature_str_mask": (8388608, 8)})
    is_clean_route_deep_slow_plus_supported: bool = field(metadata={"new_feature_str_mask": (16777216, 8)})
    is_dynamically_skip_clean_zone_supported: bool = field(metadata={"new_feature_str_mask": (33554432, 8)})
    is_dynamically_add_clean_zones_supported: bool = field(metadata={"new_feature_str_mask": (67108864, 8)})
    is_left_water_drain_supported: bool = field(metadata={"new_feature_str_mask": (134217728, 8)})
    is_clean_count_setting_supported: bool = field(metadata={"new_feature_str_mask": (1073741824, 8)})
    is_corner_clean_mode_supported: bool = field(metadata={"new_feature_str_mask": (2147483648, 8)})

    # Features from new_feature_info_str (by bit index)
    is_two_key_real_time_video_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.TWO_KEY_REAL_TIME_VIDEO}
    )
    is_two_key_rtv_in_charging_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.TWO_KEY_RTV_IN_CHARGING}
    )
    is_dirty_replenish_clean_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.DIRTY_REPLENISH_CLEAN}
    )
    is_auto_delivery_field_in_global_status_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.AUTO_DELIVERY_FIELD_IN_GLOBAL_STATUS}
    )
    is_avoid_collision_mode_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.AVOID_COLLISION_MODE}
    )
    is_voice_control_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.VOICE_CONTROL})
    is_new_endpoint_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.NEW_ENDPOINT})
    is_pumping_water_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.PUMPING_WATER})
    is_corner_mop_stretch_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.CORNER_MOP_STRETCH})
    is_hot_wash_towel_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.HOT_WASH_TOWEL})
    is_floor_dir_clean_any_time_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.FLOOR_DIR_CLEAN_ANY_TIME}
    )
    is_pet_supplies_deep_clean_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.PET_SUPPLIES_DEEP_CLEAN}
    )
    is_mop_shake_water_max_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.MOP_SHAKE_WATER_MAX}
    )
    is_exact_custom_mode_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.EXACT_CUSTOM_MODE})
    is_video_patrol_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.VIDEO_PATROL})
    is_carpet_custom_clean_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.CARPET_CUSTOM_CLEAN}
    )
    is_pet_snapshot_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.PET_SNAPSHOT})
    is_custom_clean_mode_count_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.CUSTOM_CLEAN_MODE_COUNT}
    )
    is_new_ai_recognition_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.NEW_AI_RECOGNITION})
    is_auto_collection_2_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.AUTO_COLLECTION_2})
    is_right_brush_stretch_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.RIGHT_BRUSH_STRETCH}
    )
    is_smart_clean_mode_set_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.SMART_CLEAN_MODE_SET}
    )
    is_dirty_object_detect_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.DIRTY_OBJECT_DETECT}
    )
    is_no_need_carpet_press_set_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.NO_NEED_CARPET_PRESS_SET}
    )
    is_voice_control_led_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.VOICE_CONTROL_LED})
    is_water_leak_check_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.WATER_LEAK_CHECK})
    is_min_battery_15_to_clean_task_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.MIN_BATTERY_15_TO_CLEAN_TASK}
    )
    is_gap_deep_clean_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.GAP_DEEP_CLEAN})
    is_object_detect_check_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.OBJECT_DETECT_CHECK}
    )
    is_identify_room_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.IDENTIFY_ROOM})
    is_matter_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.MATTER})
    is_workday_holiday_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.WORKDAY_HOLIDAY})
    is_clean_direct_status_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.CLEAN_DIRECT_STATUS}
    )
    is_map_eraser_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.MAP_ERASER})
    is_optimize_battery_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.OPTIMIZE_BATTERY})
    is_activate_video_charging_and_standby_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.ACTIVATE_VIDEO_CHARGING_AND_STANDBY}
    )
    is_carpet_long_haired_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.CARPET_LONG_HAIRED})
    is_clean_history_time_line_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.CLEAN_HISTORY_TIME_LINE}
    )
    is_max_zone_opened_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.MAX_ZONE_OPENED})
    is_exhibition_function_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.EXHIBITION_FUNCTION}
    )
    is_lds_lifting_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.LDS_LIFTING})
    is_auto_tear_down_mop_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.AUTO_TEAR_DOWN_MOP})
    is_small_side_mop_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.SMALL_SIDE_MOP})
    is_support_side_brush_up_down_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.SUPPORT_SIDE_BRUSH_UP_DOWN}
    )
    is_dry_interval_timer_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.DRY_INTERVAL_TIMER})
    is_uvc_sterilize_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.UVC_STERILIZE})
    is_midway_back_to_dock_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.MIDWAY_BACK_TO_DOCK}
    )
    is_support_main_brush_up_down_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.SUPPORT_MAIN_BRUSH_UP_DOWN}
    )
    is_egg_dance_mode_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.EGG_DANCE_MODE})
    is_mechanical_arm_mode_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.MECHANICAL_ARM_MODE}
    )
    is_tidyup_zones_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.TIDYUP_ZONES})
    is_clean_time_line_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.CLEAN_TIME_LINE})
    is_clean_then_mop_mode_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.CLEAN_THEN_MOP_MODE}
    )
    is_type_identify_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.TYPE_IDENTIFY})
    is_support_get_particular_status_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.SUPPORT_GET_PARTICULAR_STATUS}
    )
    is_three_d_mapping_inner_test_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.THREE_D_MAPPING_INNER_TEST}
    )
    is_sync_server_name_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.SYNC_SERVER_NAME})
    is_should_show_arm_over_load_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.SHOULD_SHOW_ARM_OVER_LOAD}
    )
    is_collect_dust_count_show_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.COLLECT_DUST_COUNT_SHOW}
    )
    is_support_api_app_stop_grasp_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.SUPPORT_API_APP_STOP_GRASP}
    )
    is_ctm_with_repeat_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.CTM_WITH_REPEAT})
    is_side_brush_lift_carpet_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.SIDE_BRUSH_LIFT_CARPET}
    )
    is_detect_wire_carpet_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.DETECT_WIRE_CARPET})
    is_water_slide_mode_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.WATER_SLIDE_MODE})
    is_soak_and_wash_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.SOAK_AND_WASH})
    is_clean_efficiency_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.CLEAN_EFFICIENCY})
    is_back_wash_new_smart_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.BACK_WASH_NEW_SMART}
    )
    is_dual_band_wi_fi_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.DUAL_BAND_WI_FI})
    is_program_mode_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.PROGRAM_MODE})
    is_clean_fluid_delivery_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.CLEAN_FLUID_DELIVERY}
    )
    is_carpet_long_haired_ex_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.CARPET_LONG_HAIRED_EX}
    )
    is_over_sea_ctm_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.OVER_SEA_CTM})
    is_full_duples_switch_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.FULL_DUPLES_SWITCH})
    is_low_area_access_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.LOW_AREA_ACCESS})
    is_follow_low_obs_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.FOLLOW_LOW_OBS})
    is_two_gears_no_collision_supported: bool = field(
        metadata={"new_feature_str_bit": NewFeatureStrBit.TWO_GEARS_NO_COLLISION}
    )
    is_carpet_shape_type_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.CARPET_SHAPE_TYPE})
    is_sr_map_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.SR_MAP})

    # Features from feature_info list
    is_led_status_switch_supported: bool = field(metadata={"robot_features": 119})
    is_multi_floor_supported: bool = field(metadata={"robot_features": 120})
    is_support_fetch_timer_summary: bool = field(metadata={"robot_features": 122})
    is_order_clean_supported: bool = field(metadata={"robot_features": 123})
    is_analysis_supported: bool = field(metadata={"robot_features": 124})
    is_remote_supported: bool = field(metadata={"robot_features": 125})
    is_support_voice_control_debug: bool = field(metadata={"robot_features": 130})

    # Features from model whitelists/blacklists or other flags
    is_mop_forbidden_supported: bool = field(
        metadata={
            "model_whitelist": [
                RoborockProductNickname.TANOSV,
                RoborockProductNickname.TOPAZSV,
                RoborockProductNickname.TANOS,
                RoborockProductNickname.TANOSE,
                RoborockProductNickname.TANOSSLITE,
                RoborockProductNickname.TANOSS,
                RoborockProductNickname.TANOSSPLUS,
                RoborockProductNickname.TANOSSMAX,
                RoborockProductNickname.ULTRON,
                RoborockProductNickname.ULTRONLITE,
                RoborockProductNickname.PEARL,
                RoborockProductNickname.RUBYSLITE,
            ]
        }
    )
    is_soft_clean_mode_supported: bool = field(
        metadata={
            "model_whitelist": [
                RoborockProductNickname.TANOSV,
                RoborockProductNickname.TANOSE,
                RoborockProductNickname.TANOS,
            ]
        }
    )
    is_custom_mode_supported: bool = field(metadata={"model_blacklist": [RoborockProductNickname.TANOS]})
    is_support_custom_carpet: bool = field(metadata={"model_whitelist": [RoborockProductNickname.ULTRONLITE]})
    is_show_general_obstacle_supported: bool = field(metadata={"model_whitelist": [RoborockProductNickname.TANOSSPLUS]})
    is_show_obstacle_photo_supported: bool = field(
        metadata={
            "model_whitelist": [
                RoborockProductNickname.TANOSSPLUS,
                RoborockProductNickname.TANOSSMAX,
                RoborockProductNickname.ULTRON,
            ]
        }
    )
    is_rubber_brush_carpet_supported: bool = field(metadata={"model_whitelist": [RoborockProductNickname.ULTRONLITE]})
    is_carpet_pressure_use_origin_paras_supported: bool = field(
        metadata={"model_whitelist": [RoborockProductNickname.ULTRONLITE]}
    )
    is_support_mop_back_pwm_set: bool = field(metadata={"model_whitelist": [RoborockProductNickname.PEARL]})
    is_collect_dust_mode_supported: bool = field(metadata={"model_blacklist": [RoborockProductNickname.PEARL]})
    is_support_water_mode: bool = field(
        metadata={
            "product_features": [
                ProductFeatures.MOP_ELECTRONIC_MODULE,
                ProductFeatures.MOP_SHAKE_MODULE,
                ProductFeatures.MOP_SPIN_MODULE,
            ]
        }
    )
    is_pure_clean_mop_supported: bool = field(metadata={"product_features": [ProductFeatures.CLEANMODE_PURECLEANMOP]})
    is_new_remote_view_supported: bool = field(metadata={"product_features": [ProductFeatures.REMOTE_BACK]})
    is_max_plus_mode_supported: bool = field(metadata={"product_features": [ProductFeatures.CLEANMODE_MAXPLUS]})
    is_none_pure_clean_mop_with_max_plus: bool = field(
        metadata={"product_features": [ProductFeatures.CLEANMODE_NONE_PURECLEANMOP_WITH_MAXPLUS]}
    )
    is_clean_route_setting_supported: bool = field(
        metadata={"product_features": [ProductFeatures.MOP_SHAKE_MODULE, ProductFeatures.MOP_SPIN_MODULE]}
    )
    is_mop_shake_module_supported: bool = field(metadata={"product_features": [ProductFeatures.MOP_SHAKE_MODULE]})
    is_customized_clean_supported: bool = field(
        metadata={"product_features": [ProductFeatures.MOP_SHAKE_MODULE, ProductFeatures.MOP_SPIN_MODULE]}
    )

    # Raw feature info values from get_init_status for diagnostics
    new_feature_info: int = field(default=0, repr=False)
    new_feature_info_str: str = field(default="", repr=False)
    feature_info: list[int] = field(default_factory=list, repr=False)

    @classmethod
    def from_feature_flags(
        cls,
        new_feature_info: int,
        new_feature_info_str: str,
        feature_info: list[int],
        product_nickname: RoborockProductNickname | None,
    ) -> Self:
        """Creates a DeviceFeatures instance from raw feature flags.
        :param new_feature_info: A int from get_init_status (sometimes can be found in homedata, but it is not always)
        :param new_feature_info_str: A hex string from get_init_status or home_data.
        :param feature_info: A list of ints from get_init_status
        :param product_nickname: The product nickname of the device."""
        # For any future reverse engineerining:
        # RobotNewFeatures = new_feature_info
        # newFeatureInfoStr = new_feature_info_str
        # feature_info =robotFeatures
        kwargs: dict[str, Any] = {
            # Store raw feature info for diagnostics
            "new_feature_info": new_feature_info,
            "new_feature_info_str": new_feature_info_str,
            "feature_info": feature_info,
        }

        for f in fields(cls):
            # Skip raw feature info fields (already set above)
            if f.name in ("new_feature_info", "new_feature_info_str", "feature_info"):
                continue
            # Default all features to False.
            kwargs[f.name] = False
            if not f.metadata:
                continue

            if (mask := f.metadata.get("robot_new_features")) is not None:
                kwargs[f.name] = bool(mask & new_feature_info)
            elif (bit_index := f.metadata.get("upper_32_bits")) is not None:
                # Check bits in the upper 32-bit integer of new_feature_info
                if new_feature_info:
                    kwargs[f.name] = bool(((new_feature_info >> 32) >> bit_index) & 1)
            elif (mask_info := f.metadata.get("new_feature_str_mask")) is not None:
                # Check bitmask against a slice of the hex string
                if new_feature_info_str:
                    try:
                        mask, slice_count = mask_info
                        if len(new_feature_info_str) >= slice_count:
                            last_chars = new_feature_info_str[-slice_count:]
                            value = int(last_chars, 16)
                            kwargs[f.name] = bool(mask & value)
                    except (ValueError, IndexError):
                        pass  # Keep it False
            elif (bit := f.metadata.get("new_feature_str_bit")) is not None:
                # Check a specific bit in the hex string using its index
                if new_feature_info_str:
                    try:
                        # Bit index defines which character and which bit inside it to check
                        char_index_from_end = 1 + bit.value // 4
                        if char_index_from_end <= len(new_feature_info_str):
                            char_hex = new_feature_info_str[-char_index_from_end]
                            nibble = int(char_hex, 16)
                            bit_in_nibble = bit.value % 4
                            kwargs[f.name] = bool((nibble >> bit_in_nibble) & 1)
                    except (ValueError, IndexError):
                        pass  # Keep it False
            elif (feature_id := f.metadata.get("robot_features")) is not None:
                kwargs[f.name] = feature_id in feature_info
            elif (whitelist := f.metadata.get("model_whitelist")) is not None:
                # If product_nickname is None, assume it is not in the whitelist
                kwargs[f.name] = product_nickname in whitelist or product_nickname is None
            elif (blacklist := f.metadata.get("model_blacklist")) is not None:
                # If product_nickname is None, assume it is not in the blacklist.
                if product_nickname is None:
                    kwargs[f.name] = True
                else:
                    kwargs[f.name] = product_nickname not in blacklist
            elif (product_features := f.metadata.get("product_features")) is not None:
                if product_nickname is not None:
                    available_features = PRODUCT_FEATURE_MAP.get(product_nickname, [])
                    if any(feat in available_features for feat in product_features):  # type: ignore
                        kwargs[f.name] = True

        return cls(**kwargs)

    def get_supported_features(self) -> list[str]:
        """Returns a list of supported features (Primarily used for logging purposes)."""
        return [k for k, v in vars(self).items() if v]


WASH_N_FILL_DOCK_TYPES = [
    RoborockDockTypeCode.empty_wash_fill_dock,
    RoborockDockTypeCode.s8_dock,
    RoborockDockTypeCode.p10_dock,
    RoborockDockTypeCode.p10_pro_dock,
    RoborockDockTypeCode.s8_maxv_ultra_dock,
    RoborockDockTypeCode.qrevo_s_dock,
    RoborockDockTypeCode.saros_r10_dock,
    RoborockDockTypeCode.qrevo_curv_dock,
    RoborockDockTypeCode.qrevo_s5v_dock,
    RoborockDockTypeCode.saros_20_dock,
]


def is_wash_n_fill_dock(dock_type: RoborockDockTypeCode) -> bool:
    """Check if the dock type is a wash and fill dock."""
    return dock_type in WASH_N_FILL_DOCK_TYPES


def is_valid_dock(dock_type: RoborockDockTypeCode) -> bool:
    """Check if device supports a dock."""
    return dock_type != RoborockDockTypeCode.no_dock
