"""
Machine-readable device catalog — the code twin of HARDWARE.md.

One entry per supported device, keyed by a short slug. Each entry says how biosync
ingests it (the Tier from HARDWARE.md) and carries the defaults a driver needs.
This is what lets "any hardware iMotions sells can be purchased and used": pick a
slug, `drivers.make(slug)` returns a ready Source.

Tiers:
  "lsl"       — vendor app emits an LSL outlet; ingested by the generic LSLSource.
                `lsl_type` / `lsl_name` select it. NO device-specific code needed.
  "brainflow" — a BrainFlow board; ingested by BrainFlowSource. `board` = BoardIds name.
  "sdk"       — closed SDK / custom protocol; a dedicated driver implements read().
  "ml"        — software source (webcam/mic + model); FEA / voice.
"""

from __future__ import annotations

from dataclasses import dataclass, field


@dataclass(frozen=True)
class Device:
    slug: str
    brand: str
    model: str
    modality: str                 # gaze / eeg / eda / ecg / emg / rsp / fnirs / fea / voice
    tier: str                     # lsl | brainflow | sdk | ml
    stype: str = ""               # LSL content-type to publish/select
    channels: tuple = ()          # default channel labels
    srate: float = 0.0            # nominal Hz (0 = irregular / unknown until connect)
    lsl_type: str = ""            # for tier=lsl: resolve upstream by this type ...
    lsl_name: str = ""            #            ... or this exact name
    board: str = ""               # for tier=brainflow: BoardIds member name
    driver: str = ""              # for tier=sdk/ml: "module:Class" implementing it
    bridge_app: str = ""          # the vendor app that emits LSL (tier=lsl)
    notes: str = ""


# --- the catalog (seeded from the iMotions 11 hardware store, June 2026) -------
DEVICES: dict[str, Device] = {d.slug: d for d in [
    # ===== Eye tracking — screen based =====
    Device("smarteye_aix", "Smart Eye", "AI-X", "gaze", "lsl", "Gaze",
           ("gaze_x", "gaze_y"), 60, lsl_type="Gaze", bridge_app="Smart Eye software"),
    Device("smarteye_aurora", "Smart Eye", "Aurora", "gaze", "lsl", "Gaze",
           ("gaze_x", "gaze_y", "pupil"), 250, lsl_type="Gaze", bridge_app="Smart Eye"),
    Device("smarteye_pro", "Smart Eye", "Pro 60Hz", "gaze", "lsl", "Gaze",
           ("gaze_x", "gaze_y"), 60, lsl_type="Gaze", bridge_app="Smart Eye Pro"),
    Device("gazepoint_gp3", "Gazepoint", "GP3 / GP3 HD", "gaze", "sdk", "Gaze",
           ("gaze_x", "gaze_y", "pupil"), 150, driver="gazepoint:GazepointSource",
           notes="open Gazepoint Open API (TCP/XML); LSL relay also exists"),
    Device("eyetech_vt3", "EyeTech", "VT3 Mini", "gaze", "sdk", "Gaze",
           ("gaze_x", "gaze_y"), 90, driver="stubs:EyeTechSource",
           notes="EyeTech TM SDK"),
    Device("tobii_spectrum", "Tobii", "Pro Spectrum", "gaze", "sdk", "Gaze",
           ("gaze_x", "gaze_y", "pupil_l", "pupil_r"), 600,
           driver="tobii:TobiiSource", notes="tobii_research SDK"),
    Device("tobii_fusion", "Tobii", "Pro Fusion", "gaze", "sdk", "Gaze",
           ("gaze_x", "gaze_y", "pupil_l", "pupil_r"), 250,
           driver="tobii:TobiiSource", notes="tobii_research SDK"),
    Device("tobii_nano", "Tobii", "Pro Nano", "gaze", "sdk", "Gaze",
           ("gaze_x", "gaze_y", "pupil_l", "pupil_r"), 60,
           driver="tobii:TobiiSource", notes="tobii_research SDK"),
    Device("tobii_glasses3", "Tobii", "Pro Glasses 3", "gaze", "sdk", "Gaze",
           ("gaze_x", "gaze_y", "pupil"), 100,
           driver="tobii:TobiiSource", notes="wearable; tobii_research / Glasses 3 API"),
    Device("tobii_pro", "Tobii", "Pro (auto-detect)", "gaze", "sdk", "Gaze",
           ("gaze_x", "gaze_y", "pupil_l", "pupil_r"), 120,
           driver="tobii:TobiiSource", notes="finds the first connected Tobii Pro tracker"),

    # ===== Eye tracking — wearable glasses =====
    Device("pupil_neon", "Pupil Labs", "Neon", "gaze", "sdk", "Gaze",
           ("gaze_x", "gaze_y", "pupil"), 200, driver="pupil:PupilNeonSource",
           notes="pupil-labs-realtime-api (richer) or Neon LSL relay"),
    Device("pupil_core", "Pupil Labs", "Core", "gaze", "lsl", "Gaze",
           ("gaze_x", "gaze_y", "pupil", "confidence"), 120, lsl_type="Gaze",
           bridge_app="Pupil Capture + LSL Relay plugin"),
    Device("viewpoint_vps", "Viewpointsystem", "VPS Smart Glasses", "gaze", "sdk",
           "Gaze", ("gaze_x", "gaze_y"), 25, driver="stubs:ViewpointSource"),
    Device("argus_etvision", "Argus Science", "ETVision", "gaze", "sdk", "Gaze",
           ("gaze_x", "gaze_y"), 180, driver="stubs:ArgusSource",
           notes="ETVision SDK; verify LSL outlet"),

    # ===== Eye tracking — VR/XR =====
    Device("varjo_xr4", "Varjo", "XR-4 / Focal", "gaze", "sdk", "Gaze",
           ("gaze_x", "gaze_y", "pupil"), 200, driver="stubs:VarjoSource",
           notes="Varjo SDK / OpenXR eye gaze -> LSL relay"),
    Device("vive_pro_eye", "HTC Vive", "Pro Eye", "gaze", "sdk", "Gaze",
           ("gaze_x", "gaze_y"), 120, driver="stubs:ViveProEyeSource",
           notes="SRanipal / OpenXR -> LSL relay"),
    Device("vive_focus", "HTC Vive", "Focus (3 / Vision)", "gaze", "sdk", "Gaze",
           ("gaze_x", "gaze_y"), 120, driver="stubs:ViveFocusSource",
           notes="standalone VR eye tracking via OpenXR / WaveSDK -> LSL relay"),
    Device("meta_quest_pro", "Meta", "Quest Pro", "gaze", "sdk", "Gaze",
           ("gaze_x", "gaze_y"), 90, driver="stubs:MetaQuestSource",
           notes="Quest Pro eye tracking via OpenXR (eye-gaze interaction) -> LSL relay"),

    # ===== EEG =====
    Device("enobio8", "Neuroelectrics", "Enobio 8", "eeg", "lsl", "EEG",
           tuple(f"ch{i+1}" for i in range(8)), 500, lsl_type="EEG",
           bridge_app="NIC2"),
    Device("enobio20", "Neuroelectrics", "Enobio 20", "eeg", "lsl", "EEG",
           tuple(f"ch{i+1}" for i in range(20)), 500, lsl_type="EEG", bridge_app="NIC2"),
    Device("enobio32", "Neuroelectrics", "Enobio 32", "eeg", "lsl", "EEG",
           tuple(f"ch{i+1}" for i in range(32)), 500, lsl_type="EEG", bridge_app="NIC2"),
    Device("balert_x10", "Advanced Brain Monitoring", "B-Alert X10", "eeg", "lsl",
           "EEG", tuple(f"ch{i+1}" for i in range(9)), 256, lsl_type="EEG",
           bridge_app="B-Alert LSL", notes="verify outlet"),
    Device("balert_x24", "Advanced Brain Monitoring", "B-Alert X24", "eeg", "lsl",
           "EEG", tuple(f"ch{i+1}" for i in range(20)), 256, lsl_type="EEG",
           bridge_app="B-Alert LSL", notes="verify outlet"),
    Device("actichamp", "Brain Products", "actiCHamp Plus", "eeg", "lsl", "EEG",
           tuple(f"ch{i+1}" for i in range(32)), 1000, lsl_type="EEG",
           bridge_app="BrainVision LSL Connector"),
    Device("bp_liveamp", "Brain Products", "LiveAmp", "eeg", "lsl", "EEG",
           tuple(f"ch{i+1}" for i in range(32)), 500, lsl_type="EEG",
           bridge_app="BrainVision LSL Connector / LiveAmp app"),
    Device("neurable_mw75", "Neurable", "MW75 Neuro", "eeg", "sdk", "EEG",
           ("ch1", "ch2", "ch3", "ch4"), 250, driver="stubs:NeurableSource",
           notes="Neurable SDK; verify LSL"),
    # boards via BrainFlow (lab-owned / add-ons)
    Device("openbci_cyton", "OpenBCI", "Cyton", "eeg", "brainflow", "EEG",
           tuple(f"ch{i+1}" for i in range(8)), 250, board="CYTON_BOARD"),
    Device("openbci_cyton_daisy", "OpenBCI", "Cyton+Daisy", "eeg", "brainflow", "EEG",
           tuple(f"ch{i+1}" for i in range(16)), 125, board="CYTON_DAISY_BOARD"),
    Device("openbci_ganglion", "OpenBCI", "Ganglion", "eeg", "brainflow", "EEG",
           tuple(f"ch{i+1}" for i in range(4)), 200, board="GANGLION_BOARD"),
    Device("muse_s", "InteraXon", "Muse S", "eeg", "brainflow", "EEG",
           ("TP9", "AF7", "AF8", "TP10"), 256, board="MUSE_S_BOARD"),
    Device("unicorn", "g.tec", "Unicorn", "eeg", "brainflow", "EEG",
           tuple(f"ch{i+1}" for i in range(8)), 250, board="UNICORN_BOARD"),

    # ===== EDA / GSR =====
    Device("shimmer_gsr", "Shimmer", "Shimmer3R GSR+", "eda", "lsl", "GSR",
           ("eda_uS",), 128, lsl_type="GSR", bridge_app="Shimmer LSL app",
           notes="or pyshimmer/Consensys via sdk"),
    Device("plux_eda", "Plux", "EDA sensor", "eda", "lsl", "GSR", ("eda_uS",), 1000,
           lsl_type="GSR", bridge_app="OpenSignals"),
    Device("biopac_eda100c", "Biopac", "EDA100C", "eda", "lsl", "GSR", ("eda_uS",),
           1000, lsl_type="GSR", bridge_app="AcqKnowledge Network Data Transfer"),

    # ===== ECG =====
    Device("shimmer_ecg", "Shimmer", "Shimmer3 ECG", "ecg", "lsl", "ECG", ("ecg_mV",),
           512, lsl_type="ECG", bridge_app="Shimmer LSL app"),
    Device("polar_h10", "Polar", "H10", "ecg", "sdk", "ECG", ("ecg_mV",), 130,
           driver="polar:PolarH10Source", notes="BLE; not in BrainFlow"),
    Device("plux_ecg", "Plux", "ECG sensor", "ecg", "lsl", "ECG", ("ecg_mV",), 1000,
           lsl_type="ECG", bridge_app="OpenSignals"),
    Device("biopac_ecg100c", "Biopac", "ECG100C", "ecg", "lsl", "ECG", ("ecg_mV",),
           1000, lsl_type="ECG", bridge_app="AcqKnowledge NDT"),

    # ===== EMG =====
    Device("shimmer_emg", "Shimmer", "Shimmer3 EMG", "emg", "lsl", "EMG", ("emg_mV",),
           512, lsl_type="EMG", bridge_app="Shimmer LSL app"),
    Device("plux_emg", "Plux", "EMG sensor", "emg", "lsl", "EMG", ("emg_mV",), 1000,
           lsl_type="EMG", bridge_app="OpenSignals"),
    Device("biopac_emg100c", "Biopac", "EMG100C", "emg", "lsl", "EMG", ("emg_mV",),
           1000, lsl_type="EMG", bridge_app="AcqKnowledge NDT"),

    # ===== Respiration =====
    Device("plux_rip", "Plux", "Inductive Respiration (RIP)", "rsp", "lsl", "RSP",
           ("rsp",), 1000, lsl_type="Respiration", bridge_app="OpenSignals"),
    Device("respiban", "Plux", "respiBAN BLE", "rsp", "lsl", "RSP", ("rsp",), 1000,
           lsl_type="Respiration", bridge_app="OpenSignals"),
    Device("biopac_rsp100c", "Biopac", "RSP100C", "rsp", "lsl", "RSP", ("rsp",), 1000,
           lsl_type="Respiration", bridge_app="AcqKnowledge NDT"),

    # ===== fNIRS =====
    Device("artinis_brite", "Artinis", "Brite", "fnirs", "lsl", "NIRS",
           ("O2Hb", "HHb"), 50, lsl_type="NIRS", bridge_app="OxySoft / Brite Connect"),
    Device("artinis_brite_frontal", "Artinis", "Brite Frontal", "fnirs", "lsl", "NIRS",
           ("O2Hb", "HHb"), 50, lsl_type="NIRS", bridge_app="OxySoft"),
    Device("artinis_babybrite", "Artinis", "BabyBrite", "fnirs", "lsl", "NIRS",
           ("O2Hb", "HHb"), 50, lsl_type="NIRS", bridge_app="OxySoft"),

    # ===== Software / AI (no external sensor) =====
    Device("fea_webcam", "open (py-feat)", "Facial Expression Analysis", "fea", "ml",
           "FEA", ("AU01", "AU02", "AU04", "AU06", "AU12", "valence", "arousal"), 10,
           driver="fea:FEASource", notes="replaces locked Affectiva; any webcam"),
    Device("voice_mic", "open (Whisper+openSMILE)", "Voice Analysis", "voice", "ml",
           "Voice", ("loudness", "pitch_hz", "jitter", "shimmer"), 0,
           driver="voice:VoiceSource", notes="replaces AssemblyAI/audEERING; any mic"),
]}


def by_modality(modality: str) -> list[Device]:
    return [d for d in DEVICES.values() if d.modality == modality]


def by_tier(tier: str) -> list[Device]:
    return [d for d in DEVICES.values() if d.tier == tier]
