/*
    * Bluetooth.cpp
    * Top-level Bluetooth subsystem — adapter registration and Intel BT initialization
    * Copyright (c) 2026 Daniel Hammer
*/

#include "Bluetooth.hpp"
#include "Hci.hpp"
#include "A2dp.hpp"
#include "IntelFirmware.hpp"
#include <Drivers/USB/Xhci.hpp>
#include <Drivers/USB/UsbDevice.hpp>
#include <Fs/Vfs.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Libraries/Memory.hpp>
#include <Timekeeping/ApicTimer.hpp>
#include <Hal/SmpBoot.hpp>
#include <atomic>

using namespace Kt;

namespace Drivers::USB::Bluetooth {

    // =========================================================================
    // State
    // =========================================================================

    static bool g_initialized = false;
    static uint8_t g_slotId = 0;
    static uint8_t g_bdAddr[6] = {};

    // True when the USB transport is up but the firmware-dependent HCI init is
    // still waiting for the ramdisk (drive 0) to be mounted.  Set when an
    // adapter enumerates during the boot port scan, which runs before the boot
    // filesystems are mounted; claimed (atomically -- the pickup runs from the
    // idle loop, concurrently with the rest of the system) by
    // ServiceDeferredInit() once VFS is up.
    static std::atomic<bool> g_initPending{false};

    // Forward declaration: firmware-dependent HCI bring-up, run once VFS is up.
    static void CompleteInit();

    // Path to the Bluetooth config (BD_ADDR override).  Written by the
    // Bluetooth desktop app; read here on boot.  See ApplyConfiguredAddress().
    static constexpr const char* BT_CONFIG_PATH = "0:/config/bluetooth.toml";

    // =========================================================================
    // bluetooth.toml BD_ADDR override
    // =========================================================================

    static bool HexNibble(char c, uint8_t& out) {
        if (c >= '0' && c <= '9') { out = (uint8_t)(c - '0'); return true; }
        if (c >= 'a' && c <= 'f') { out = (uint8_t)(c - 'a' + 10); return true; }
        if (c >= 'A' && c <= 'F') { out = (uint8_t)(c - 'A' + 10); return true; }
        return false;
    }

    // Parse exactly six ':'/'-'-separated hex octets from a string fragment.
    // Fills out[0..5] in written order (out[0] is the first printed octet,
    // matching the desktop app's format_addr); returns false on any deviation.
    static bool ParseMacStr(const char* s, int len, uint8_t out[6]) {
        int byteIdx = 0, i = 0;
        while (byteIdx < 6) {
            while (i < len && (s[i] == ':' || s[i] == '-' || s[i] == ' ')) i++;
            uint8_t hi = 0, lo = 0;
            if (i + 1 >= len || !HexNibble(s[i], hi) || !HexNibble(s[i + 1], lo))
                return false;
            out[byteIdx++] = (uint8_t)((hi << 4) | lo);
            i += 2;
        }
        return true;
    }

    // Read the optional `mac = "XX:XX:XX:XX:XX:XX"` key from bluetooth.toml.
    // Minimal line scanner (the kernel has no TOML parser, and the file holds a
    // single value); ignores section headers/comments.  Returns false when the
    // file is absent or the key is missing/malformed.
    static bool ReadConfiguredMac(uint8_t out[6]) {
        Fs::Vfs::BackendFile f;
        if (Fs::Vfs::OpenBackendFile(BT_CONFIG_PATH, f) < 0) return false;
        uint64_t size = Fs::Vfs::GetBackendFileSize(f);
        if (size == 0) { Fs::Vfs::CloseBackendFile(f); return false; }

        char buf[512];
        uint64_t n = size < sizeof(buf) - 1 ? size : sizeof(buf) - 1;
        Fs::Vfs::ReadBackendFile(f, (uint8_t*)buf, 0, n);
        Fs::Vfs::CloseBackendFile(f);
        buf[n] = '\0';

        const char* p = buf;
        while (*p) {
            while (*p == ' ' || *p == '\t') p++;
            // Match a bare "mac" key (next char must end the identifier).
            if (p[0] == 'm' && p[1] == 'a' && p[2] == 'c' &&
                (p[3] == ' ' || p[3] == '\t' || p[3] == '=')) {
                const char* q = p + 3;
                while (*q == ' ' || *q == '\t') q++;
                if (*q == '=') {
                    const char* quote = q + 1;
                    while (*quote && *quote != '"' && *quote != '\n') quote++;
                    if (*quote == '"') {
                        const char* end = quote + 1;
                        while (*end && *end != '"' && *end != '\n') end++;
                        if (*end == '"' &&
                            ParseMacStr(quote + 1, (int)(end - quote - 1), out))
                            return true;
                    }
                }
            }
            while (*p && *p != '\n') p++;
            if (*p == '\n') p++;
        }
        return false;
    }

    // Apply a configured BD_ADDR override (if any) to the freshly-reset
    // controller.  Called from CompleteInit BEFORE ReadBdAddr so the rest of
    // bring-up uses the overridden address; no HCI Reset must follow (the Intel
    // 0xFC31 override is volatile and a reset reverts to the factory address).
    static void ApplyConfiguredAddress() {
        uint8_t mac[6];
        if (!ReadConfiguredMac(mac)) return;   // no override configured
        if (Hci::SetBdAddr(mac)) {
            KernelLogStream(OK, "BT") << "Applied BD_ADDR override from bluetooth.toml";
        } else {
            KernelLogStream(WARNING, "BT")
                << "BD_ADDR override from bluetooth.toml rejected by controller";
        }
    }

    // Intel Bluetooth device IDs
    static bool IsIntelBt(uint16_t vid, uint16_t pid) {
        if (vid != 0x8087) return false;
        // Known Intel Bluetooth USB product IDs
        switch (pid) {
            case 0x0032:  // AX211 variant
            case 0x0033:  // AX211
            case 0x0036:  // AX211 variant
            case 0x0038:  // AX211 variant
            case 0x0AAA:  // AX200
            case 0x0026:  // AX201
            case 0x0029:  // AX201 variant
            case 0x0025:  // 9560
            case 0x0A2B:  // 8265
            case 0x0A2A:  // 8260
            case 0x07DC:  // 8265 variant
            case 0x0AA7:  // AX200 variant
                return true;
            default:
                return false;
        }
    }

    // =========================================================================
    // Intel Bluetooth firmware detection
    // =========================================================================

    static bool InitIntelBluetooth(uint8_t slotId) {
        KernelLogStream(INFO, "BT") << "Intel Bluetooth adapter detected";

        // Intel BT controllers require HCI Reset before they respond to
        // vendor-specific commands.  This mirrors the Linux btintel driver
        // sequence: Reset → Read Version → (firmware load) → Reset.
        if (!Hci::Reset()) {
            KernelLogStream(ERROR, "BT") << "Initial HCI Reset failed";
            return false;
        }

        // Read standard HCI version -- if this fails, the controller is likely
        // in bootloader mode where only vendor commands are accepted.
        Hci::LocalVersion lver = {};
        bool hciVersionOk = Hci::ReadLocalVersion(&lver);
        if (hciVersionOk) {
            KernelLogStream(INFO, "BT") << "HCI version=" << (uint64_t)lver.HciVersion
                << " rev=" << base::hex << (uint64_t)lver.HciRevision
                << " LMP=" << (uint64_t)lver.LmpVersion
                << " manufacturer=" << (uint64_t)lver.Manufacturer
                << " subver=" << (uint64_t)lver.LmpSubversion << base::dec;
        }

        // Read legacy Intel version for diagnostics (TLV parts return this in
        // a different layout; the authoritative state check happens inside the
        // firmware download path below via the TLV version).
        Hci::IntelVersion ver = {};
        if (!Hci::ReadIntelVersion(&ver)) {
            KernelLogStream(WARNING, "BT") << "Failed to read Intel BT version";
        } else {
            KernelLogStream(INFO, "BT") << "Intel BT: HW variant=" << (uint64_t)ver.HwVariant
                << " FW variant=" << base::hex << (uint64_t)ver.FwVariant
                << " FW rev=" << (uint64_t)ver.FwRevision << "."
                << (uint64_t)ver.FwBuildNum << base::dec;
        }

        // Run the firmware download path.  This reads the TLV version, and if
        // the controller is in bootloader mode, loads the matching .sfi image
        // from the ramdisk, secure-sends it, boots the operational firmware
        // and applies DDC parameters.  Returns true if the controller ends up
        // operational (either already loaded, or freshly downloaded).
        if (!DownloadIntelFirmware()) {
            KernelLogStream(WARNING, "BT")
                << "Intel BT firmware not loaded; limited functionality";
            // Standard init already issued an HCI Reset above.
            return true;
        }

        // The operational firmware just (re)booted.  Give it a clean reset and
        // re-enable the Intel vendor event mask before the generic HCI setup.
        Hci::Reset();
        Hci::IntelSetEventMask();
        return true;
    }

    // =========================================================================
    // RegisterAdapter — entry point from USB enumeration
    // =========================================================================

    void RegisterAdapter(uint8_t slotId) {
        if (g_initialized) {
            KernelLogStream(WARNING, "BT") << "Bluetooth adapter already registered";
            return;
        }

        g_slotId = slotId;

        // Initialize HCI transport (allocates DMA buffers, registers callback)
        // NOTE: Does NOT queue receive transfers yet — device isn't ready
        Hci::Initialize(slotId);

        auto* dev = Xhci::GetDevice(slotId);
        if (!dev) return;

        // Wait for the USB device to be ready after SET_CONFIGURATION
        // Intel BT controllers need 200-500ms after config before accepting HCI
        uint64_t start = Timekeeping::GetMilliseconds();
        while (Timekeeping::GetMilliseconds() - start < 200) {
            Xhci::PollEvents();
            asm volatile("pause" ::: "memory");
        }

        // Start the event pipe BEFORE sending any HCI commands.
        // HCI command responses arrive as events on the interrupt IN endpoint,
        // so it must be queued to receive them.  Deliberately done HERE, at
        // enumeration time, not in the deferred bring-up: this preserves the
        // exact transport timing of the original synchronous boot path.
        Hci::StartEventPipe();

        // The firmware download path reads the .sfi/.ddc images from the
        // ramdisk (drive 0).  Adapters present at boot enumerate during the
        // xHCI port scan, which runs before the boot filesystems are mounted,
        // so defer the firmware-dependent bring-up until VFS is available.
        // The idle loop (ServiceDeferredInit) picks it up after boot, keeping
        // the multi-second firmware download off the boot-critical path.
        if (!Fs::Vfs::IsDriveRegistered(0)) {
            g_initPending.store(true, std::memory_order_release);
            KernelLogStream(INFO, "BT") << "Transport up; deferring init until ramdisk is mounted";
            return;
        }

        CompleteInit();
    }

    // =========================================================================
    // CompleteInit — firmware-dependent HCI bring-up (needs VFS/ramdisk)
    // =========================================================================

    static void CompleteInit() {
        auto* dev = Xhci::GetDevice(g_slotId);
        if (!dev) return;

        // Intel-specific initialization (firmware download + HCI Reset)
        bool didReset = false;
        if (IsIntelBt(dev->VendorId, dev->ProductId)) {
            if (InitIntelBluetooth(g_slotId)) {
                didReset = true;  // InitIntelBluetooth already sent HCI Reset
            } else {
                KernelLogStream(WARNING, "BT") << "Intel BT init failed, continuing with basic HCI";
            }
        }

        // Standard HCI Reset (skip if Intel init already did one)
        if (!didReset) {
            if (!Hci::Reset()) {
                KernelLogStream(ERROR, "BT") << "HCI Reset failed";
                return;
            }
        }

        // Apply a persisted BD_ADDR override (0:/config/bluetooth.toml) now,
        // after the last reset and before the address is read back, so the
        // whole bring-up below uses the overridden address.
        ApplyConfiguredAddress();

        // Read BD_ADDR
        if (Hci::ReadBdAddr(g_bdAddr)) {
            KernelLogStream(OK, "BT") << "BD_ADDR: "
                << base::hex
                << (uint64_t)g_bdAddr[5] << ":" << (uint64_t)g_bdAddr[4] << ":"
                << (uint64_t)g_bdAddr[3] << ":" << (uint64_t)g_bdAddr[2] << ":"
                << (uint64_t)g_bdAddr[1] << ":" << (uint64_t)g_bdAddr[0] << base::dec;
        }

        // NOTE: do NOT override the BD_ADDR via 0xFC31 here.  The BD_ADDR is an
        // input to the SSP authentication confirmation, and if the override only
        // changes the advertised address but not the address the firmware uses
        // in the crypto, the two sides compute different confirmations and
        // pairing fails (Simple Pairing Complete = 0x05).  Use the real address.

        // Read buffer size
        uint16_t aclLen = 0, aclNum = 0;
        uint8_t scoLen = 0;
        uint16_t scoNum = 0;
        if (Hci::ReadBufferSize(&aclLen, &scoLen, &aclNum, &scoNum)) {
            KernelLogStream(INFO, "BT") << "ACL buffer: " << (uint64_t)aclLen
                << " bytes x " << (uint64_t)aclNum;
        }

        // Set local name
        Hci::WriteLocalName("MontaukOS");

        // Class of Device.  The A2DP spec MANDATES the Capturing service bit
        // (0x080000) for a source; Audio (0x200000) is customary.  Major/minor
        // class: Computer/Laptop (0x010C) -- what we actually are.  The old
        // value 0x200408 (Audio/Video major class, minor "hands-free device",
        // no Capturing bit) presented MontaukOS to the headset as ANOTHER
        // HEADSET.  A sink's connection manager classifies peers by CoD (it
        // arrives in its Connection Request event and is cached at pairing),
        // and an A2DP/AVRCP dial from a "hands-free unit" is a credible reason
        // for it to park those channels at "authorization pending" forever.
        // NOTE: the headset caches this from pairing -- it must FORGET the
        // device and re-pair to observe the new class.
        Hci::WriteClassOfDevice(0x28010C);

        // Enable Simple Secure Pairing
        Hci::WriteSSPMode(1);

        // Allow role switch + sniff on new connections.  The controller default
        // link policy is 0x0000 (deny both).  A multipoint headset (Bose QC
        // Ultra) that also holds a link to a phone requests a role switch to
        // master on our link to avoid a scatternet; with the switch denied,
        // some sink firmwares never grant the A2DP media path.  Sniff denial
        // similarly upsets CSR-derived stacks that sniff idle links.
        uint8_t linkPolicy[2] = {0x05, 0x00};   // bit0 role switch, bit2 sniff
        Hci::SendCommand(Hci::OP_WRITE_DEFAULT_LP, linkPolicy, 2);
        Hci::WaitCommandComplete(Hci::OP_WRITE_DEFAULT_LP);

        // Set event mask to receive relevant events.  Octet 6 (events 0x31-0x38)
        // MUST be enabled for Secure Simple Pairing: IO Capability Request
        // (0x31, bit 48), IO Capability Response (0x32), User Confirmation
        // Request (0x33), Simple Pairing Complete (0x36) all live there.  It was
        // 0x00 -> the controller started SSP but the IO-Capability Request event
        // never reached us, so pairing always timed out with auth failure 0x05.
        uint8_t eventMask[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0x20};
        Hci::SendCommand(Hci::OP_SET_EVENT_MASK, eventMask, 8);
        Hci::WaitCommandComplete(Hci::OP_SET_EVENT_MASK);

        // Enable inquiry + page scan (discoverable and connectable)
        Hci::WriteScanEnable(0x03);

        // Load persisted bonds so previously-paired devices reconnect without
        // re-pairing (VFS is up by the time CompleteInit runs).
        Hci::LoadLinkKeys();

        g_initialized = true;
        KernelLogStream(OK, "BT") << "Bluetooth adapter initialized successfully";
    }

    // =========================================================================
    // ServiceDeferredInit — run boot-deferred bring-up once VFS is ready
    // =========================================================================

    void ServiceDeferredInit() {
        if (!g_initPending.load(std::memory_order_relaxed) || g_initialized) return;
        if (!Fs::Vfs::IsDriveRegistered(0)) return;   // ramdisk still not mounted
        if (Xhci::InPollContext()) return;            // never nest under PollEvents

        // Claim the pending init (this runs from the idle loop; make sure only
        // one pass performs the bring-up).  The firmware download inside takes
        // seconds -- running it here instead of on the boot path is what keeps
        // boot fast.
        bool expected = true;
        if (!g_initPending.compare_exchange_strong(expected, false,
                                                   std::memory_order_acquire)) return;

        KernelLogStream(INFO, "BT") << "Completing deferred Bluetooth init in background";

        // Reserve this CPU for the duration.  The bring-up overlaps desktop
        // startup, and if the scheduler tick pulls this idle context away
        // whenever a process is ready, the HCI waits' wall-clock timeouts
        // expire with almost no polling done.  Reserved, the bring-up runs
        // uninterrupted here while processes use other CPUs; on a single-CPU
        // system this briefly pauses userspace, matching the old synchronous
        // behavior minus the boot-path stall.
        auto* cpu = Smp::GetCurrentCpuData();
        if (cpu) cpu->reservedForKernelWork = true;
        Hci::SetFwTrace(true);    // bounded per-completion event-pipe trace
        CompleteInit();
        Hci::DumpFwTrace();       // flush remaining records (process context)
        Hci::SetFwTrace(false);
        if (cpu) cpu->reservedForKernelWork = false;
    }

    // =========================================================================
    // ServiceEvents — steady-state event pump (idle loop)
    // =========================================================================

    void ServiceEvents() {
        if (!g_initialized) return;
        if (Xhci::InPollContext()) return;   // never nest under PollEvents
        Xhci::PollEvents();
        Hci::DrainEvents();
        Hci::ProcessPendingCommands();
        A2dp::PumpMedia();   // feed queued media to the link (no-op when idle)
    }

    // =========================================================================
    // Public queries
    // =========================================================================

    bool IsInitialized() {
        return g_initialized;
    }

    uint8_t GetSlotId() {
        return g_slotId;
    }

    const uint8_t* GetBdAddr() {
        return g_bdAddr;
    }

    // =========================================================================
    // SetAddress — live BD_ADDR change
    // =========================================================================

    bool SetAddress(const uint8_t* addr) {
        if (!g_initialized || !addr) return false;

        // Reject the obviously-invalid addresses (all-zero, broadcast).
        bool allZero = true, allOnes = true;
        for (int i = 0; i < 6; i++) {
            if (addr[i] != 0x00) allZero = false;
            if (addr[i] != 0xFF) allOnes = false;
        }
        if (allZero || allOnes) return false;

        // Drop any active link first: its pairing/encryption was negotiated
        // against the old address, so it cannot survive the change.
        bool droppedLink = false;
        for (int i = 0; i < Hci::MAX_CONNECTIONS; i++) {
            auto* conn = Hci::GetConnectionByIndex(i);
            if (conn && conn->Active) {
                Hci::Disconnect(conn->Handle, 0x13);  // Remote User Terminated
                droppedLink = true;
            }
        }
        // Let the disconnection(s) complete before reprogramming the address.
        if (droppedLink) {
            uint64_t t0 = Timekeeping::GetMilliseconds();
            while (Timekeeping::GetMilliseconds() - t0 < 300) {
                Xhci::PollEvents();
                Hci::DrainEvents();
                for (int k = 0; k < 200; k++) asm volatile("pause" ::: "memory");
            }
        }

        // Program the new address.  Do NOT issue an HCI Reset afterwards: the
        // Intel 0xFC31 override is volatile and a reset reverts it.
        if (!Hci::SetBdAddr(addr)) return false;

        // Re-read to confirm and refresh the cache; fall back to the requested
        // bytes if the read fails.
        uint8_t readback[6] = {};
        if (Hci::ReadBdAddr(readback)) {
            memcpy(g_bdAddr, readback, 6);
        } else {
            memcpy(g_bdAddr, addr, 6);
        }

        // Re-assert discoverable + connectable with the new address (no reset
        // happened, so name/CoD/SSP/event-mask all persist).
        Hci::WriteScanEnable(0x03);

        for (int i = 0; i < 6; i++) {
            if (g_bdAddr[i] != addr[i]) return false;
        }
        KernelLogStream(OK, "BT") << "BD_ADDR changed at runtime";
        return true;
    }

    // =========================================================================
    // Scan — blocking inquiry
    // =========================================================================

    int Scan(Hci::InquiryDevice* buf, int maxCount, uint32_t timeoutMs) {
        if (!g_initialized || !buf || maxCount <= 0) return -1;

        Hci::ClearInquiryResults();

        // Convert timeout to 1.28s units (min 1, max 30)
        uint8_t duration = (uint8_t)(timeoutMs / 1280);
        if (duration < 1) duration = 1;
        if (duration > 30) duration = 30;

        if (!Hci::StartInquiry(duration)) return -1;

        // Poll until inquiry completes or timeout
        uint64_t start = Timekeeping::GetMilliseconds();
        while (Hci::IsInquiryActive() && (Timekeeping::GetMilliseconds() - start < timeoutMs)) {
            Xhci::PollEvents();
            Hci::DrainEvents();

            for (int j = 0; j < 200; j++) {
                asm volatile("pause" ::: "memory");
            }
        }

        // Cancel if still running
        if (Hci::IsInquiryActive()) {
            Hci::CancelInquiry();
        }

        return Hci::GetInquiryResults(buf, maxCount);
    }

    // =========================================================================
    // Connect — initiate ACL connection
    // =========================================================================

    int Connect(const uint8_t* bdAddr, uint32_t timeoutMs) {
        if (!g_initialized || !bdAddr) return -1;

        if (!Hci::CreateConnection(bdAddr)) return -1;

        // Wait for Connection Complete event
        uint64_t start = Timekeeping::GetMilliseconds();
        while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
            Xhci::PollEvents();
            Hci::DrainEvents();

            // Check connection table for matching BD_ADDR
            for (int i = 0; i < Hci::MAX_CONNECTIONS; i++) {
                auto* conn = Hci::GetConnectionByIndex(i);
                if (conn && conn->Active) {
                    bool match = true;
                    for (int j = 0; j < 6; j++) {
                        if (conn->BdAddr[j] != bdAddr[j]) { match = false; break; }
                    }
                    if (match) {
                        // The headset (in pairing mode) drives Secure Simple
                        // Pairing itself right after the ACL link comes up.  Let
                        // authentication + encryption finish BEFORE opening any
                        // L2CAP/AVDTP channels: doing A2DP on a not-yet-
                        // authenticated link races with the pairing handshake and
                        // the headset drops us (reason 0x05).  Drain events here
                        // so the IO-capability / user-confirm / link-key / encrypt
                        // events all get serviced.
                        //
                        // We are the initiator: request authentication so the
                        // controller starts Secure Simple Pairing (Link Key
                        // Request -> our negative reply -> IO Capability Request
                        // -> ... ).  The headset does not start this on its own.
                        // NB this only works now that octet 6 of the event mask
                        // is enabled so the IO-Capability Request event actually
                        // reaches us; before that this produced 03 17 06 05.
                        Hci::AuthenticateLink(conn->Handle);

                        uint64_t t0 = Timekeeping::GetMilliseconds();
                        while (Timekeeping::GetMilliseconds() - t0 < 5000) {
                            Xhci::PollEvents();
                            Hci::DrainEvents();
                            // Send queued pairing replies reliably (top-level,
                            // not nested under PollEvents).
                            Hci::ProcessPendingCommands();
                            if (!conn->Active) break;     // link dropped during pairing
                            if (conn->Encrypted) break;   // authenticated + encrypted -> ready
                            for (int k = 0; k < 200; k++) asm volatile("pause" ::: "memory");
                        }

                        // Bring up the A2DP source stream (signaling + media
                        // channels, SBC negotiation) only once the link is
                        // secured.  Without a media stream the headset also drops
                        // the link (reason 0x13), so this keeps it engaged too.
                        if (conn->Active) {
                            // Let the link settle after Encryption Change before
                            // dialing L2CAP: some sinks ignore a CONN_REQ that
                            // arrives the instant encryption completes.  Drain
                            // (don't blind-sleep) so the ACL RX ring stays live.
                            uint64_t st = Timekeeping::GetMilliseconds();
                            while (Timekeeping::GetMilliseconds() - st < 300) {
                                Xhci::PollEvents();
                                Hci::DrainEvents();
                                for (int k = 0; k < 200; k++) asm volatile("pause" ::: "memory");
                            }
                            A2dp::StartSource();
                        }
                        // Persist any new link key now (process context), even if
                        // the link later dropped, so the disk write never stalls
                        // the nested pairing event handler.
                        Hci::FlushLinkKeys();
                        return 0;
                    }
                }
            }

            for (int j = 0; j < 200; j++) {
                asm volatile("pause" ::: "memory");
            }
        }

        return -1; // Timeout
    }

    // =========================================================================
    // Disconnect — disconnect a device by BD_ADDR
    // =========================================================================

    int Disconnect(const uint8_t* bdAddr) {
        if (!g_initialized || !bdAddr) return -1;

        // Find connection with matching BD_ADDR
        for (int i = 0; i < Hci::MAX_CONNECTIONS; i++) {
            auto* conn = Hci::GetConnectionByIndex(i);
            if (conn && conn->Active) {
                bool match = true;
                for (int j = 0; j < 6; j++) {
                    if (conn->BdAddr[j] != bdAddr[j]) { match = false; break; }
                }
                if (match) {
                    Hci::Disconnect(conn->Handle, 0x13); // 0x13 = Remote User Terminated
                    return 0;
                }
            }
        }

        return -1; // Not found
    }

    // =========================================================================
    // ListConnected — list active connections
    // =========================================================================

    int ListConnected(Hci::ConnectionInfo* buf, int maxCount) {
        if (!g_initialized || !buf || maxCount <= 0) return 0;

        int count = 0;
        for (int i = 0; i < Hci::MAX_CONNECTIONS && count < maxCount; i++) {
            auto* conn = Hci::GetConnectionByIndex(i);
            if (conn && conn->Active) {
                buf[count] = *conn;
                count++;
            }
        }
        return count;
    }

    // =========================================================================
    // ListBonded / ForgetDevice — paired-device management
    // =========================================================================

    int ListBonded(Hci::BondInfo* buf, int maxCount) {
        if (!g_initialized || !buf || maxCount <= 0) return 0;
        return Hci::ListBonds(buf, maxCount);
    }

    int ForgetDevice(const uint8_t* bdAddr) {
        if (!g_initialized || !bdAddr) return -1;
        // Best-effort: tear down an active link first so we don't keep a live
        // connection whose key we just discarded.
        Disconnect(bdAddr);
        return Hci::ForgetBond(bdAddr) ? 0 : -1;
    }

}
